Add onboarding component
This commit is contained in:
BIN
src/assets/app-icon-white.png
Normal file
BIN
src/assets/app-icon-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -11,6 +11,8 @@ const Background = styled.View`
|
|||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Modal: React.FC = ({ children }) => {
|
const Modal: React.FC = ({ children }) => {
|
||||||
|
|||||||
76
src/screens/Onboarding/index.tsx
Normal file
76
src/screens/Onboarding/index.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import { Button } from 'react-native';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NavigationProp } from 'screens';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { setOnboardingStatus } from 'store/settings/actions';
|
||||||
|
|
||||||
|
const Container = styled.SafeAreaView`
|
||||||
|
background-color: ${THEME_COLOR};
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TextContainer = styled.ScrollView`
|
||||||
|
padding: 25px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Text = styled.Text`
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonContainer = styled.View`
|
||||||
|
margin-top: 50px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Logo = styled.Image`
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
margin: 0 auto 50px auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function Onboarding() {
|
||||||
|
// Get account from Redux and dispatcher
|
||||||
|
const account = useTypedSelector(state => state.settings.jellyfin);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
// Also retrieve the navigation handler so that we can open the modal in
|
||||||
|
// which the Jellyfin server is set
|
||||||
|
const navigation = useNavigation<NavigationProp>();
|
||||||
|
const handleClick = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
|
||||||
|
|
||||||
|
// We'll also respond to any change in the account, setting the onboarding
|
||||||
|
// status to true, so that the app becomes available.
|
||||||
|
useEffect(() => {
|
||||||
|
if (account) {
|
||||||
|
dispatch(setOnboardingStatus(true));
|
||||||
|
}
|
||||||
|
}, [account, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<TextContainer contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
|
||||||
|
<Logo source={require('../../assets/app-icon-white.png')} />
|
||||||
|
<Text >
|
||||||
|
Welcome!
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
Jellyfin Audio Player will allow you to stream your music library from anywhere, with full support for background audio and casting.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
In order to get started, you need a Jellyfin server. Click the button below to enter your Jellyfin server address and login to it.
|
||||||
|
</Text>
|
||||||
|
<ButtonContainer>
|
||||||
|
<Button title="Set Jellyfin Server" color="#ffffff" onPress={handleClick} />
|
||||||
|
</ButtonContainer>
|
||||||
|
</TextContainer>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Onboarding;
|
||||||
@@ -10,6 +10,8 @@ import PlayPauseIcon from 'assets/play-pause-fill.svg';
|
|||||||
import NotesIcon from 'assets/notes.svg';
|
import NotesIcon from 'assets/notes.svg';
|
||||||
import GearIcon from 'assets/gear.svg';
|
import GearIcon from 'assets/gear.svg';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import Onboarding from './Onboarding';
|
||||||
|
|
||||||
const Stack = createStackNavigator();
|
const Stack = createStackNavigator();
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
@@ -34,6 +36,14 @@ function getIcon(route: string): React.FC<any> | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Screens() {
|
function Screens() {
|
||||||
|
const isOnboardingComplete = useTypedSelector(state => state.settings.isOnboardingComplete);
|
||||||
|
|
||||||
|
// GUARD: If onboarding has not been completed, we instead render the
|
||||||
|
// onboarding component, so that the user can get setup in the app.
|
||||||
|
if (!isOnboardingComplete) {
|
||||||
|
return <Onboarding />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
screenOptions={({ route }) => ({
|
screenOptions={({ route }) => ({
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ export default function SetJellyfinServer() {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View style={{ padding: 20 }}>
|
<View style={{ padding: 20 }}>
|
||||||
<Text style={colors.text}>Please enter your Jellyfin server URL first. Make sure to include the protocol and port</Text>
|
<Text style={colors.text}>
|
||||||
|
Please enter your Jellyfin server URL. Make sure to include the protocol and port
|
||||||
|
</Text>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://jellyfin.yourserver.io/"
|
placeholder="https://jellyfin.yourserver.io/"
|
||||||
onChangeText={setServerUrl}
|
onChangeText={setServerUrl}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { configureStore, getDefaultMiddleware, combineReducers } from '@reduxjs/toolkit';
|
import { configureStore, getDefaultMiddleware, combineReducers } from '@reduxjs/toolkit';
|
||||||
import { useSelector, TypedUseSelectorHook } from 'react-redux';
|
import { useSelector, TypedUseSelectorHook } from 'react-redux';
|
||||||
import AsyncStorage from '@react-native-community/async-storage';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import { persistStore, persistReducer } from 'redux-persist';
|
import { persistStore, persistReducer, PersistConfig } from 'redux-persist';
|
||||||
|
import autoMergeLevel2 from 'redux-persist/es/stateReconciler/autoMergeLevel2';
|
||||||
// import logger from 'redux-logger';
|
// import logger from 'redux-logger';
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig: PersistConfig<AppState> = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
storage: AsyncStorage,
|
storage: AsyncStorage,
|
||||||
|
stateReconciler: autoMergeLevel2
|
||||||
};
|
};
|
||||||
|
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
@@ -26,7 +28,7 @@ const store = configureStore({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppState = ReturnType<typeof store.getState>;
|
export type AppState = ReturnType<typeof reducers>;
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
||||||
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
export const setJellyfinCredentials = createAction<{ access_token: string, user_id: string, uri: string, deviced_id: string; }>('SET_JELLYFIN_CREDENTIALS');
|
export const setJellyfinCredentials = createAction<{ access_token: string, user_id: string, uri: string, deviced_id: string; }>('SET_JELLYFIN_CREDENTIALS');
|
||||||
export const setBitrate = createAction<number>('SET_BITRATE');
|
export const setBitrate = createAction<number>('SET_BITRATE');
|
||||||
|
export const setOnboardingStatus = createAction<boolean>('SET_ONBOARDING_STATUS');
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import { setBitrate, setJellyfinCredentials } from './actions';
|
import { setBitrate, setJellyfinCredentials, setOnboardingStatus } from './actions';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
jellyfin?: {
|
jellyfin?: {
|
||||||
@@ -9,10 +9,12 @@ interface State {
|
|||||||
device_id: string;
|
device_id: string;
|
||||||
}
|
}
|
||||||
bitrate: number;
|
bitrate: number;
|
||||||
|
isOnboardingComplete: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
bitrate: 140000000
|
bitrate: 140000000,
|
||||||
|
isOnboardingComplete: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const settings = createReducer(initialState, {
|
const settings = createReducer(initialState, {
|
||||||
@@ -24,6 +26,10 @@ const settings = createReducer(initialState, {
|
|||||||
...state,
|
...state,
|
||||||
bitrate: action.payload,
|
bitrate: action.payload,
|
||||||
}),
|
}),
|
||||||
|
[setOnboardingStatus.type]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
isOnboardingComplete: action.payload,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export default settings;
|
export default settings;
|
||||||
Reference in New Issue
Block a user