mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {useCallback} from 'react'
2import Animated, {
3 FadeInUp,
4 FadeOutUp,
5 LayoutAnimationConfig,
6 LinearTransition,
7} from 'react-native-reanimated'
8import {msg, Trans} from '@lingui/macro'
9import {useLingui} from '@lingui/react'
10
11import {IS_INTERNAL} from '#/lib/app-info'
12import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
13import {useGate} from '#/lib/statsig/statsig'
14import {isNative} from '#/platform/detection'
15import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
16import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem'
17import {atoms as a, native, useAlf, useTheme} from '#/alf'
18import * as ToggleButton from '#/components/forms/ToggleButton'
19import {Props as SVGIconProps} from '#/components/icons/common'
20import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
21import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
22import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
23import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
24import * as Layout from '#/components/Layout'
25import {Text} from '#/components/Typography'
26import * as SettingsList from './components/SettingsList'
27
28type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
29export function AppearanceSettingsScreen({}: Props) {
30 const {_} = useLingui()
31 const {fonts} = useAlf()
32 const gate = useGate()
33
34 const {colorMode, darkTheme} = useThemePrefs()
35 const {setColorMode, setDarkTheme} = useSetThemePrefs()
36
37 const onChangeAppearance = useCallback(
38 (keys: string[]) => {
39 const appearance = keys.find(key => key !== colorMode) as
40 | 'system'
41 | 'light'
42 | 'dark'
43 | undefined
44 if (!appearance) return
45 setColorMode(appearance)
46 },
47 [setColorMode, colorMode],
48 )
49
50 const onChangeDarkTheme = useCallback(
51 (keys: string[]) => {
52 const theme = keys.find(key => key !== darkTheme) as
53 | 'dim'
54 | 'dark'
55 | undefined
56 if (!theme) return
57 setDarkTheme(theme)
58 },
59 [setDarkTheme, darkTheme],
60 )
61
62 const onChangeFontFamily = useCallback(
63 (values: string[]) => {
64 const next = values[0] === 'system' ? 'system' : 'theme'
65 fonts.setFontFamily(next)
66 },
67 [fonts],
68 )
69
70 const onChangeFontScale = useCallback(
71 (values: string[]) => {
72 const next = values[0] || ('0' as any)
73 fonts.setFontScale(next)
74 },
75 [fonts],
76 )
77
78 return (
79 <LayoutAnimationConfig skipExiting skipEntering>
80 <Layout.Screen testID="preferencesThreadsScreen">
81 <Layout.Header.Outer>
82 <Layout.Header.BackButton />
83 <Layout.Header.Content>
84 <Layout.Header.TitleText>
85 <Trans>Appearance</Trans>
86 </Layout.Header.TitleText>
87 </Layout.Header.Content>
88 <Layout.Header.Slot />
89 </Layout.Header.Outer>
90 <Layout.Content>
91 <SettingsList.Container>
92 <AppearanceToggleButtonGroup
93 title={_(msg`Color mode`)}
94 icon={PhoneIcon}
95 items={[
96 {
97 label: _(msg`System`),
98 name: 'system',
99 },
100 {
101 label: _(msg`Light`),
102 name: 'light',
103 },
104 {
105 label: _(msg`Dark`),
106 name: 'dark',
107 },
108 ]}
109 values={[colorMode]}
110 onChange={onChangeAppearance}
111 />
112
113 {colorMode !== 'light' && (
114 <Animated.View
115 entering={native(FadeInUp)}
116 exiting={native(FadeOutUp)}>
117 <AppearanceToggleButtonGroup
118 title={_(msg`Dark theme`)}
119 icon={MoonIcon}
120 items={[
121 {
122 label: _(msg`Dim`),
123 name: 'dim',
124 },
125 {
126 label: _(msg`Dark`),
127 name: 'dark',
128 },
129 ]}
130 values={[darkTheme ?? 'dim']}
131 onChange={onChangeDarkTheme}
132 />
133 </Animated.View>
134 )}
135
136 <Animated.View layout={native(LinearTransition)}>
137 <SettingsList.Divider />
138
139 <AppearanceToggleButtonGroup
140 title={_(msg`Font`)}
141 description={_(
142 msg`For the best experience, we recommend using the theme font.`,
143 )}
144 icon={Aa}
145 items={[
146 {
147 label: _(msg`System`),
148 name: 'system',
149 },
150 {
151 label: _(msg`Theme`),
152 name: 'theme',
153 },
154 ]}
155 values={[fonts.family]}
156 onChange={onChangeFontFamily}
157 />
158
159 <AppearanceToggleButtonGroup
160 title={_(msg`Font size`)}
161 icon={TextSize}
162 items={[
163 {
164 label: _(msg`Smaller`),
165 name: '-1',
166 },
167 {
168 label: _(msg`Default`),
169 name: '0',
170 },
171 {
172 label: _(msg`Larger`),
173 name: '1',
174 },
175 ]}
176 values={[fonts.scale]}
177 onChange={onChangeFontScale}
178 />
179
180 {isNative && IS_INTERNAL && gate('debug_subscriptions') && (
181 <>
182 <SettingsList.Divider />
183 <AppIconSettingsListItem />
184 </>
185 )}
186 </Animated.View>
187 </SettingsList.Container>
188 </Layout.Content>
189 </Layout.Screen>
190 </LayoutAnimationConfig>
191 )
192}
193
194export function AppearanceToggleButtonGroup({
195 title,
196 description,
197 icon: Icon,
198 items,
199 values,
200 onChange,
201}: {
202 title: string
203 description?: string
204 icon: React.ComponentType<SVGIconProps>
205 items: {
206 label: string
207 name: string
208 }[]
209 values: string[]
210 onChange: (values: string[]) => void
211}) {
212 const t = useTheme()
213 return (
214 <>
215 <SettingsList.Group contentContainerStyle={[a.gap_sm]} iconInset={false}>
216 <SettingsList.ItemIcon icon={Icon} />
217 <SettingsList.ItemText>{title}</SettingsList.ItemText>
218 {description && (
219 <Text
220 style={[
221 a.text_sm,
222 a.leading_snug,
223 t.atoms.text_contrast_medium,
224 a.w_full,
225 ]}>
226 {description}
227 </Text>
228 )}
229 <ToggleButton.Group label={title} values={values} onChange={onChange}>
230 {items.map(item => (
231 <ToggleButton.Button
232 key={item.name}
233 label={item.label}
234 name={item.name}>
235 <ToggleButton.ButtonText>{item.label}</ToggleButton.ButtonText>
236 </ToggleButton.Button>
237 ))}
238 </ToggleButton.Group>
239 </SettingsList.Group>
240 </>
241 )
242}