feat: allow users to override color scheme (closes #138)
This commit is contained in:
3
src/assets/icons/checkmark.svg
Normal file
3
src/assets/icons/checkmark.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36719 17.2363C6.78711 17.2363 7.11914 17.0508 7.35352 16.6895L16.582 2.1582C16.7578 1.875 16.8262 1.66016 16.8262 1.43555C16.8262 0.898438 16.4746 0.546875 15.9375 0.546875C15.5469 0.546875 15.332 0.673828 15.0977 1.04492L6.32812 15.0195L1.77734 9.0625C1.5332 8.7207 1.28906 8.58398 0.9375 8.58398C0.380859 8.58398 0 8.96484 0 9.50195C0 9.72656 0.0976562 9.98047 0.283203 10.2148L5.35156 16.6699C5.64453 17.0508 5.94727 17.2363 6.36719 17.2363Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 563 B |
@@ -1,18 +1,18 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { PropsWithChildren, useEffect } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import TrackPlayer, { Capability } from 'react-native-track-player';
|
import TrackPlayer, { Capability } from 'react-native-track-player';
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import Routes from '../screens';
|
import Routes from '../screens';
|
||||||
import store, { persistedStore } from 'store';
|
import store, { persistedStore, useTypedSelector } from 'store';
|
||||||
import {
|
import {
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
DarkTheme as BaseDarkTheme,
|
DarkTheme as BaseDarkTheme,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { useColorScheme } from 'react-native';
|
import { ColorSchemeProvider, themes } from './Colors';
|
||||||
import { ColorSchemeContext, themes } from './Colors';
|
|
||||||
import DownloadManager from './DownloadManager';
|
import DownloadManager from './DownloadManager';
|
||||||
// import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
import { useColorScheme } from 'react-native';
|
||||||
|
import { ColorScheme } from 'store/settings/types';
|
||||||
|
|
||||||
const LightTheme = {
|
const LightTheme = {
|
||||||
...DefaultTheme,
|
...DefaultTheme,
|
||||||
@@ -30,14 +30,29 @@ const DarkTheme = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a convenience wrapper for NavigationContainer that ensures that the
|
||||||
|
* right theme is selected based on OS color scheme settings along with user preferences.
|
||||||
|
*/
|
||||||
|
function ThemedNavigationContainer({ children }: PropsWithChildren<{}>) {
|
||||||
|
const systemScheme = useColorScheme();
|
||||||
|
const userScheme = useTypedSelector((state) => state.settings.colorScheme);
|
||||||
|
const scheme = userScheme === ColorScheme.System ? systemScheme : userScheme;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavigationContainer
|
||||||
|
theme={scheme === 'dark' ? DarkTheme : LightTheme}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Track whether the player has already been setup, so that we don't
|
// Track whether the player has already been setup, so that we don't
|
||||||
// accidentally do it twice.
|
// accidentally do it twice.
|
||||||
let hasSetupPlayer = false;
|
let hasSetupPlayer = false;
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
export default function App(): JSX.Element {
|
||||||
const colorScheme = useColorScheme();
|
|
||||||
const theme = themes[colorScheme || 'light'];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function setupTrackPlayer() {
|
async function setupTrackPlayer() {
|
||||||
await TrackPlayer.setupPlayer();
|
await TrackPlayer.setupPlayer();
|
||||||
@@ -63,14 +78,12 @@ export default function App(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistedStore}>
|
<PersistGate loading={null} persistor={persistedStore}>
|
||||||
<ColorSchemeContext.Provider value={theme}>
|
<ColorSchemeProvider>
|
||||||
<NavigationContainer
|
<ThemedNavigationContainer>
|
||||||
theme={colorScheme === 'dark' ? DarkTheme : LightTheme}
|
|
||||||
>
|
|
||||||
<Routes />
|
<Routes />
|
||||||
<DownloadManager />
|
<DownloadManager />
|
||||||
</NavigationContainer>
|
</ThemedNavigationContainer>
|
||||||
</ColorSchemeContext.Provider>
|
</ColorSchemeProvider>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { THEME_COLOR } from 'CONSTANTS';
|
|||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { ColorSchemeName, Platform, StyleSheet, View, useColorScheme } from 'react-native';
|
import { ColorSchemeName, Platform, StyleSheet, View, useColorScheme } from 'react-native';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { ColorScheme } from 'store/settings/types';
|
||||||
|
|
||||||
const majorPlatformVersion = typeof Platform.Version === 'string' ? parseInt(Platform.Version, 10) : Platform.Version;
|
const majorPlatformVersion = typeof Platform.Version === 'string' ? parseInt(Platform.Version, 10) : Platform.Version;
|
||||||
|
|
||||||
@@ -77,6 +79,22 @@ export const themes: Record<'dark' | 'light', ReturnType<typeof generateStyles>>
|
|||||||
// Create context for supplying the theming information
|
// Create context for supplying the theming information
|
||||||
export const ColorSchemeContext = React.createContext(themes.dark);
|
export const ColorSchemeContext = React.createContext(themes.dark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This provider contains the logic for settings the right theme on the ColorSchemeContext.
|
||||||
|
*/
|
||||||
|
export function ColorSchemeProvider({ children }: PropsWithChildren<{}>) {
|
||||||
|
const systemScheme = useColorScheme();
|
||||||
|
const userScheme = useTypedSelector((state) => state.settings.colorScheme);
|
||||||
|
const scheme = userScheme === ColorScheme.System ? systemScheme : userScheme;
|
||||||
|
const theme = themes[scheme || 'light'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColorSchemeContext.Provider value={theme}>
|
||||||
|
{children}
|
||||||
|
</ColorSchemeContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the default styles object in hook form
|
* Retrieves the default styles object in hook form
|
||||||
*/
|
*/
|
||||||
@@ -98,13 +116,15 @@ export function DefaultStylesProvider(props: DefaultStylesProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ColoredBlurView(props: PropsWithChildren<BlurViewProps>) {
|
export function ColoredBlurView(props: PropsWithChildren<BlurViewProps>) {
|
||||||
const scheme = useColorScheme();
|
const systemScheme = useColorScheme();
|
||||||
|
const userScheme = useTypedSelector((state) => state.settings.colorScheme);
|
||||||
|
const scheme = userScheme === ColorScheme.System ? systemScheme : userScheme;
|
||||||
|
|
||||||
return Platform.OS === 'ios' ? (
|
return Platform.OS === 'ios' ? (
|
||||||
<BlurView
|
<BlurView
|
||||||
{...props}
|
{...props}
|
||||||
blurType={Platform.OS === 'ios' && majorPlatformVersion >= 13
|
blurType={Platform.OS === 'ios' && majorPlatformVersion >= 13
|
||||||
? 'material'
|
? scheme === 'dark' ? 'materialDark' : 'materialLight'
|
||||||
: scheme === 'dark' ? 'extraDark' : 'xlight'
|
: scheme === 'dark' ? 'extraDark' : 'xlight'
|
||||||
} />
|
} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -63,5 +63,10 @@
|
|||||||
"total-duration": "Total duration",
|
"total-duration": "Total duration",
|
||||||
"similar-albums": "Similar albums",
|
"similar-albums": "Similar albums",
|
||||||
"playback-reporting": "Playback Reporting",
|
"playback-reporting": "Playback Reporting",
|
||||||
"playback-reporting-description": "With Playback Reporting, all your playback events are relayed back to Jellyfin. This allows you to track your most listened songs, particularly with Jellyfin plugins such as ListenBrainz."
|
"playback-reporting-description": "With Playback Reporting, all your playback events are relayed back to Jellyfin. This allows you to track your most listened songs, particularly with Jellyfin plugins such as ListenBrainz.",
|
||||||
|
"color-scheme": "Color Scheme",
|
||||||
|
"color-scheme-description": "By default, Fintunes will follow your operating system's color scheme. You can however choose to override this to make sure Fintunes is always in dark mode or light mode.",
|
||||||
|
"color-scheme-system": "System",
|
||||||
|
"color-scheme-light": "Light Mode",
|
||||||
|
"color-scheme-dark": "Dark Mode"
|
||||||
}
|
}
|
||||||
@@ -62,3 +62,8 @@ export type LocaleKeys = 'play-next'
|
|||||||
| 'similar-albums'
|
| 'similar-albums'
|
||||||
| 'playback-reporting'
|
| 'playback-reporting'
|
||||||
| 'playback-reporting-description'
|
| 'playback-reporting-description'
|
||||||
|
| 'color-scheme'
|
||||||
|
| 'color-scheme-description'
|
||||||
|
| 'color-scheme-system'
|
||||||
|
| 'color-scheme-light'
|
||||||
|
| 'color-scheme-dark'
|
||||||
8
src/screens/Settings/components/Container.tsx
Normal file
8
src/screens/Settings/components/Container.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { SafeScrollView } from 'components/SafeNavigatorView';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled(SafeScrollView)`
|
||||||
|
padding: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Container;
|
||||||
11
src/screens/Settings/components/Input.tsx
Normal file
11
src/screens/Settings/components/Input.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import styled from 'styled-components/native';
|
||||||
|
|
||||||
|
export const InputContainer = styled.View`
|
||||||
|
margin: 10px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Input = styled.TextInput`
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
`;
|
||||||
61
src/screens/Settings/components/Radio.tsx
Normal file
61
src/screens/Settings/components/Radio.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
import CheckmarkIcon from 'assets/icons/checkmark.svg';
|
||||||
|
import { Text } from 'components/Typography';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import { Gap } from 'components/Utility';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
export const RadioList = styled.View`
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RadioItemContainer = styled.Pressable<{ checked?: boolean }>`
|
||||||
|
padding: 16px 24px 16px 16px;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface RadioItemProps<T> {
|
||||||
|
checked?: boolean;
|
||||||
|
label?: string;
|
||||||
|
value: T;
|
||||||
|
onPress: (value: T) => void;
|
||||||
|
last?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RadioItem<T>({
|
||||||
|
checked,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onPress,
|
||||||
|
last
|
||||||
|
}: RadioItemProps<T>) {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
|
const handlePress = useCallback(() => {
|
||||||
|
onPress(value);
|
||||||
|
}, [onPress, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={!last ? { borderBottomWidth: 1, borderBottomColor: defaultStyles.divider.backgroundColor } : undefined}>
|
||||||
|
<RadioItemContainer
|
||||||
|
onPress={handlePress}
|
||||||
|
style={({ pressed }) => [
|
||||||
|
{ backgroundColor: pressed
|
||||||
|
? defaultStyles.activeBackground.backgroundColor
|
||||||
|
: defaultStyles.button.backgroundColor
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{checked ? <CheckmarkIcon fill={THEME_COLOR} height={14} width={14} /> : <Gap size={14} />}
|
||||||
|
<Gap size={8} />
|
||||||
|
<Text style={checked && { color: THEME_COLOR }}>{label}</Text>
|
||||||
|
</RadioItemContainer>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/screens/Settings/components/Switch.tsx
Normal file
13
src/screens/Settings/components/Switch.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Text } from 'components/Typography';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
|
||||||
|
export const SwitchContainer = styled.View`
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 16px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SwitchLabel = styled(Text)`
|
||||||
|
font-size: 16px;
|
||||||
|
`;
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import Library from './components/Library';
|
|
||||||
import Cache from './components/Cache';
|
|
||||||
import useDefaultStyles, { ColoredBlurView } from 'components/Colors';
|
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import ListButton from 'components/ListButton';
|
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import Sentry from './components/Sentry';
|
import ListButton from 'components/ListButton';
|
||||||
|
import useDefaultStyles, { ColoredBlurView } from 'components/Colors';
|
||||||
|
|
||||||
import { SettingsNavigationProp } from './types';
|
import { SettingsNavigationProp } from './types';
|
||||||
|
|
||||||
|
import Cache from './stacks/Cache';
|
||||||
|
import Sentry from './stacks/Sentry';
|
||||||
|
import Library from './stacks/Library';
|
||||||
|
import ColorScheme from './stacks/ColorScheme';
|
||||||
|
import PlaybackReporting from './stacks/PlaybackReporting';
|
||||||
import { SafeScrollView } from 'components/SafeNavigatorView';
|
import { SafeScrollView } from 'components/SafeNavigatorView';
|
||||||
import PlaybackReporting from './components/PlaybackReporting';
|
|
||||||
|
|
||||||
export function SettingsList() {
|
export function SettingsList() {
|
||||||
const navigation = useNavigation<SettingsNavigationProp>();
|
const navigation = useNavigation<SettingsNavigationProp>();
|
||||||
@@ -19,6 +22,7 @@ export function SettingsList() {
|
|||||||
const handleCacheClick = useCallback(() => { navigation.navigate('Cache'); }, [navigation]);
|
const handleCacheClick = useCallback(() => { navigation.navigate('Cache'); }, [navigation]);
|
||||||
const handleSentryClick = useCallback(() => { navigation.navigate('Sentry'); }, [navigation]);
|
const handleSentryClick = useCallback(() => { navigation.navigate('Sentry'); }, [navigation]);
|
||||||
const handlePlaybackReportingClick = useCallback(() => { navigation.navigate('Playback Reporting'); }, [navigation]);
|
const handlePlaybackReportingClick = useCallback(() => { navigation.navigate('Playback Reporting'); }, [navigation]);
|
||||||
|
const handleColorSchemeClick = useCallback(() => { navigation.navigate('Color Scheme'); }, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeScrollView>
|
<SafeScrollView>
|
||||||
@@ -26,6 +30,7 @@ export function SettingsList() {
|
|||||||
<ListButton onPress={handleCacheClick}>{t('setting-cache')}</ListButton>
|
<ListButton onPress={handleCacheClick}>{t('setting-cache')}</ListButton>
|
||||||
<ListButton onPress={handleSentryClick}>{t('error-reporting')}</ListButton>
|
<ListButton onPress={handleSentryClick}>{t('error-reporting')}</ListButton>
|
||||||
<ListButton onPress={handlePlaybackReportingClick}>{t('playback-reporting')}</ListButton>
|
<ListButton onPress={handlePlaybackReportingClick}>{t('playback-reporting')}</ListButton>
|
||||||
|
<ListButton onPress={handleColorSchemeClick}>{t('color-scheme')}</ListButton>
|
||||||
</SafeScrollView>
|
</SafeScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -47,6 +52,7 @@ export default function Settings() {
|
|||||||
<Stack.Screen name="Cache" component={Cache} options={{ headerTitle: t('setting-cache') }} />
|
<Stack.Screen name="Cache" component={Cache} options={{ headerTitle: t('setting-cache') }} />
|
||||||
<Stack.Screen name="Sentry" component={Sentry} options={{ headerTitle: t('error-reporting') }} />
|
<Stack.Screen name="Sentry" component={Sentry} options={{ headerTitle: t('error-reporting') }} />
|
||||||
<Stack.Screen name="Playback Reporting" component={PlaybackReporting} options={{ headerTitle: t('playback-reporting')}} />
|
<Stack.Screen name="Playback Reporting" component={PlaybackReporting} options={{ headerTitle: t('playback-reporting')}} />
|
||||||
|
<Stack.Screen name="Color Scheme" component={ColorScheme} options={{ headerTitle: t('color-scheme')}} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,16 +6,12 @@ import Button from 'components/Button';
|
|||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { Paragraph } from 'components/Typography';
|
import { Paragraph } from 'components/Typography';
|
||||||
import { useAppDispatch } from 'store';
|
import { useAppDispatch } from 'store';
|
||||||
import { SafeScrollView } from 'components/SafeNavigatorView';
|
import Container from '../components/Container';
|
||||||
|
|
||||||
const ClearCache = styled(Button)`
|
const ClearCache = styled(Button)`
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Container = styled(SafeScrollView)`
|
|
||||||
padding: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function CacheSettings() {
|
export default function CacheSettings() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const handleClearCache = useCallback(() => {
|
const handleClearCache = useCallback(() => {
|
||||||
45
src/screens/Settings/stacks/ColorScheme.tsx
Normal file
45
src/screens/Settings/stacks/ColorScheme.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { Paragraph } from 'components/Typography';
|
||||||
|
import Container from '../components/Container';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import { RadioItem, RadioList } from '../components/Radio';
|
||||||
|
import { ColorScheme } from 'store/settings/types';
|
||||||
|
import { useAppDispatch, useTypedSelector } from 'store';
|
||||||
|
import { setColorScheme } from 'store/settings/actions';
|
||||||
|
|
||||||
|
export default function ColorSchemeSetting() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const scheme = useTypedSelector((state) => state.settings.colorScheme);
|
||||||
|
|
||||||
|
const handlePress = useCallback((value: ColorScheme) => {
|
||||||
|
dispatch(setColorScheme(value));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Paragraph>{t('color-scheme-description')}</Paragraph>
|
||||||
|
<Paragraph />
|
||||||
|
<RadioList>
|
||||||
|
<RadioItem
|
||||||
|
label={t('color-scheme-system')}
|
||||||
|
value={ColorScheme.System}
|
||||||
|
onPress={handlePress}
|
||||||
|
checked={scheme === ColorScheme.System}
|
||||||
|
/>
|
||||||
|
<RadioItem
|
||||||
|
label={t('color-scheme-light')}
|
||||||
|
value={ColorScheme.Light}
|
||||||
|
onPress={handlePress}
|
||||||
|
checked={scheme === ColorScheme.Light}
|
||||||
|
/>
|
||||||
|
<RadioItem
|
||||||
|
label={t('color-scheme-dark')}
|
||||||
|
value={ColorScheme.Dark}
|
||||||
|
onPress={handlePress}
|
||||||
|
checked={scheme === ColorScheme.Dark}
|
||||||
|
last
|
||||||
|
/>
|
||||||
|
</RadioList>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import styled from 'styled-components/native';
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import useDefaultStyles from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
@@ -7,21 +6,8 @@ import { useTypedSelector } from 'store';
|
|||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
import { Paragraph } from 'components/Typography';
|
import { Paragraph } from 'components/Typography';
|
||||||
import { SafeScrollView } from 'components/SafeNavigatorView';
|
import Container from '../components/Container';
|
||||||
|
import { InputContainer, Input } from '../components/Input';
|
||||||
const InputContainer = styled.View`
|
|
||||||
margin: 10px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Input = styled.TextInput`
|
|
||||||
padding: 15px;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Container = styled(SafeScrollView)`
|
|
||||||
padding: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function LibrarySettings() {
|
export default function LibrarySettings() {
|
||||||
const defaultStyles = useDefaultStyles();
|
const defaultStyles = useDefaultStyles();
|
||||||
@@ -1,26 +1,12 @@
|
|||||||
import { Paragraph, Text } from 'components/Typography';
|
import { Paragraph } from 'components/Typography';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Switch } from 'react-native-gesture-handler';
|
import { Switch } from 'react-native-gesture-handler';
|
||||||
import styled from 'styled-components/native';
|
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
import { SafeScrollView } from 'components/SafeNavigatorView';
|
import { SafeScrollView } from 'components/SafeNavigatorView';
|
||||||
import { useAppDispatch, useTypedSelector } from 'store';
|
import { useAppDispatch, useTypedSelector } from 'store';
|
||||||
import { setEnablePlaybackReporting } from 'store/settings/actions';
|
import { setEnablePlaybackReporting } from 'store/settings/actions';
|
||||||
|
import Container from '../components/Container';
|
||||||
const Container = styled.View`
|
import { SwitchContainer, SwitchLabel } from '../components/Switch';
|
||||||
padding: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SwitchContainer = styled.View`
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin: 16px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Label = styled(Text)`
|
|
||||||
font-size: 16px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function PlaybackReporting() {
|
export default function PlaybackReporting() {
|
||||||
const isEnabled = useTypedSelector((state) => state.settings.enablePlaybackReporting);
|
const isEnabled = useTypedSelector((state) => state.settings.enablePlaybackReporting);
|
||||||
@@ -35,7 +21,7 @@ export default function PlaybackReporting() {
|
|||||||
<Container>
|
<Container>
|
||||||
<Paragraph>{t('playback-reporting-description')}</Paragraph>
|
<Paragraph>{t('playback-reporting-description')}</Paragraph>
|
||||||
<SwitchContainer>
|
<SwitchContainer>
|
||||||
<Label>{t('playback-reporting')}</Label>
|
<SwitchLabel>{t('playback-reporting')}</SwitchLabel>
|
||||||
<Switch value={isEnabled} onValueChange={toggleSwitch} />
|
<Switch value={isEnabled} onValueChange={toggleSwitch} />
|
||||||
</SwitchContainer>
|
</SwitchContainer>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import Sentry from 'screens/Settings/components/Sentry';
|
import Sentry from 'screens/Settings/stacks/Sentry';
|
||||||
|
|
||||||
export default function ErrorReportingPopup() {
|
export default function ErrorReportingPopup() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import settings from './settings';
|
|||||||
import music, { initialState as musicInitialState } from './music';
|
import music, { initialState as musicInitialState } from './music';
|
||||||
import downloads, { initialState as downloadsInitialState } from './downloads';
|
import downloads, { initialState as downloadsInitialState } from './downloads';
|
||||||
import { PersistState } from 'redux-persist/es/types';
|
import { PersistState } from 'redux-persist/es/types';
|
||||||
|
import { ColorScheme } from './settings/types';
|
||||||
|
|
||||||
const persistConfig: PersistConfig<Omit<AppState, '_persist'>> = {
|
const persistConfig: PersistConfig<Omit<AppState, '_persist'>> = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
@@ -44,6 +45,16 @@ const persistConfig: PersistConfig<Omit<AppState, '_persist'>> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
// @ts-expect-error migrations are poorly typed
|
||||||
|
4: (state: AppState) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
colorScheme: ColorScheme.System,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import { ColorScheme } from './types';
|
||||||
|
|
||||||
export const setJellyfinCredentials = createAction<{ access_token: string, user_id: string, uri: string, device_id: string; }>('SET_JELLYFIN_CREDENTIALS');
|
export const setJellyfinCredentials = createAction<{ access_token: string, user_id: string, uri: string, device_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');
|
export const setOnboardingStatus = createAction<boolean>('SET_ONBOARDING_STATUS');
|
||||||
export const setReceivedErrorReportingAlert = createAction<void>('SET_RECEIVED_ERROR_REPORTING_ALERT');
|
export const setReceivedErrorReportingAlert = createAction<void>('SET_RECEIVED_ERROR_REPORTING_ALERT');
|
||||||
export const setEnablePlaybackReporting = createAction<boolean>('SET_ENABLE_PLAYBACK_REPORTING');
|
export const setEnablePlaybackReporting = createAction<boolean>('SET_ENABLE_PLAYBACK_REPORTING');
|
||||||
|
export const setColorScheme = createAction<ColorScheme>('SET_COLOR_SCHEME');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus, setEnablePlaybackReporting } from './actions';
|
import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus, setEnablePlaybackReporting, setColorScheme } from './actions';
|
||||||
|
import { ColorScheme } from './types';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
jellyfin?: {
|
jellyfin?: {
|
||||||
@@ -12,6 +13,7 @@ interface State {
|
|||||||
isOnboardingComplete: boolean;
|
isOnboardingComplete: boolean;
|
||||||
hasReceivedErrorReportingAlert: boolean;
|
hasReceivedErrorReportingAlert: boolean;
|
||||||
enablePlaybackReporting: boolean;
|
enablePlaybackReporting: boolean;
|
||||||
|
colorScheme: ColorScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
@@ -19,6 +21,7 @@ const initialState: State = {
|
|||||||
isOnboardingComplete: false,
|
isOnboardingComplete: false,
|
||||||
hasReceivedErrorReportingAlert: false,
|
hasReceivedErrorReportingAlert: false,
|
||||||
enablePlaybackReporting: true,
|
enablePlaybackReporting: true,
|
||||||
|
colorScheme: ColorScheme.System,
|
||||||
};
|
};
|
||||||
|
|
||||||
const settings = createReducer(initialState, builder => {
|
const settings = createReducer(initialState, builder => {
|
||||||
@@ -42,6 +45,10 @@ const settings = createReducer(initialState, builder => {
|
|||||||
...state,
|
...state,
|
||||||
enablePlaybackReporting: action.payload,
|
enablePlaybackReporting: action.payload,
|
||||||
}));
|
}));
|
||||||
|
builder.addCase(setColorScheme, (state, action) => ({
|
||||||
|
...state,
|
||||||
|
colorScheme: action.payload,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
export default settings;
|
export default settings;
|
||||||
5
src/store/settings/types.ts
Normal file
5
src/store/settings/types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum ColorScheme {
|
||||||
|
System = 'system',
|
||||||
|
Light = 'light',
|
||||||
|
Dark = 'dark',
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user