diff --git a/src/App.native.tsx b/src/App.native.tsx index 865e6dc195..ccc7de32d8 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -24,6 +24,7 @@ import {TestCtrls} from 'view/com/testing/TestCtrls' import {Provider as ShellStateProvider} from 'state/shell' import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as InvitesStateProvider} from 'state/invites' +import {Provider as PrefsStateProvider} from 'state/preferences' SplashScreen.preventAutoHideAsync() @@ -80,11 +81,13 @@ function App() { return ( - - - - - + + + + + + + ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index cfc2a0028b..363161bfc2 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -19,6 +19,7 @@ import {queryClient} from 'lib/react-query' import {Provider as ShellStateProvider} from 'state/shell' import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as InvitesStateProvider} from 'state/invites' +import {Provider as PrefsStateProvider} from 'state/preferences' const InnerApp = observer(function AppImpl() { const colorMode = useColorMode() @@ -70,11 +71,13 @@ function App() { return ( - - - - - + + + + + + + ) } diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index d03fa8d24a..9514865920 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -10,11 +10,10 @@ import {isObj, hasProp} from 'lib/type-guards' import {RootStoreModel} from '../root-store' import {ModerationOpts} from '@atproto/api' import {DEFAULT_FEEDS} from 'lib/constants' -import {deviceLocales} from 'platform/detection' import {getAge} from 'lib/strings/time' import {FeedTuner} from 'lib/api/feed-manip' -import {LANGUAGES} from '../../../locale/languages' import {logger} from '#/logger' +import {getContentLanguages} from '#/state/preferences/languages' // TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf export type LabelPreference = APILabelPreference | 'show' @@ -34,9 +33,6 @@ const LABEL_GROUPS = [ 'impersonation', ] const VISIBILITY_VALUES = ['ignore', 'warn', 'hide'] -const DEFAULT_LANG_CODES = (deviceLocales || []) - .concat(['en', 'ja', 'pt', 'de']) - .slice(0, 6) const THREAD_SORT_VALUES = ['oldest', 'newest', 'most-likes', 'random'] interface LegacyPreferences { @@ -62,10 +58,6 @@ export class LabelPreferencesModel { export class PreferencesModel { adultContentEnabled = false - primaryLanguage: string = deviceLocales[0] || 'en' - contentLanguages: string[] = deviceLocales || [] - postLanguage: string = deviceLocales[0] || 'en' - postLanguageHistory: string[] = DEFAULT_LANG_CODES contentLabels = new LabelPreferencesModel() savedFeeds: string[] = [] pinnedFeeds: string[] = [] @@ -103,10 +95,6 @@ export class PreferencesModel { serialize() { return { - primaryLanguage: this.primaryLanguage, - contentLanguages: this.contentLanguages, - postLanguage: this.postLanguage, - postLanguageHistory: this.postLanguageHistory, contentLabels: this.contentLabels, savedFeeds: this.savedFeeds, pinnedFeeds: this.pinnedFeeds, @@ -120,44 +108,6 @@ export class PreferencesModel { */ hydrate(v: unknown) { if (isObj(v)) { - if ( - hasProp(v, 'primaryLanguage') && - typeof v.primaryLanguage === 'string' - ) { - this.primaryLanguage = v.primaryLanguage - } else { - // default to the device languages - this.primaryLanguage = deviceLocales[0] || 'en' - } - // check if content languages in preferences exist, otherwise default to device languages - if ( - hasProp(v, 'contentLanguages') && - Array.isArray(v.contentLanguages) && - typeof v.contentLanguages.every(item => typeof item === 'string') - ) { - this.contentLanguages = v.contentLanguages - } else { - // default to the device languages - this.contentLanguages = deviceLocales - } - if (hasProp(v, 'postLanguage') && typeof v.postLanguage === 'string') { - this.postLanguage = v.postLanguage - } else { - // default to the device languages - this.postLanguage = deviceLocales[0] || 'en' - } - if ( - hasProp(v, 'postLanguageHistory') && - Array.isArray(v.postLanguageHistory) && - typeof v.postLanguageHistory.every(item => typeof item === 'string') - ) { - this.postLanguageHistory = v.postLanguageHistory - .concat(DEFAULT_LANG_CODES) - .slice(0, 6) - } else { - // default to a starter set - this.postLanguageHistory = DEFAULT_LANG_CODES - } // check if content labels in preferences exist, then hydrate if (hasProp(v, 'contentLabels') && typeof v.contentLabels === 'object') { Object.assign(this.contentLabels, v.contentLabels) @@ -262,9 +212,6 @@ export class PreferencesModel { try { runInAction(() => { this.contentLabels = new LabelPreferencesModel() - this.contentLanguages = deviceLocales - this.postLanguage = deviceLocales ? deviceLocales.join(',') : 'en' - this.postLanguageHistory = DEFAULT_LANG_CODES this.savedFeeds = [] this.pinnedFeeds = [] }) @@ -276,81 +223,6 @@ export class PreferencesModel { } } - // languages - // = - - hasContentLanguage(code2: string) { - return this.contentLanguages.includes(code2) - } - - toggleContentLanguage(code2: string) { - if (this.hasContentLanguage(code2)) { - this.contentLanguages = this.contentLanguages.filter( - lang => lang !== code2, - ) - } else { - this.contentLanguages = this.contentLanguages.concat([code2]) - } - } - - /** - * A getter that splits `this.postLanguage` into an array of strings. - * - * This was previously the main field on this model, but now we're - * concatenating lang codes to make multi-selection a little better. - */ - get postLanguages() { - // filter out empty strings if exist - return this.postLanguage.split(',').filter(Boolean) - } - - hasPostLanguage(code2: string) { - return this.postLanguages.includes(code2) - } - - togglePostLanguage(code2: string) { - if (this.hasPostLanguage(code2)) { - this.postLanguage = this.postLanguages - .filter(lang => lang !== code2) - .join(',') - } else { - // sort alphabetically for deterministic comparison in context menu - this.postLanguage = this.postLanguages - .concat([code2]) - .sort((a, b) => a.localeCompare(b)) - .join(',') - } - } - - setPostLanguage(commaSeparatedLangCodes: string) { - this.postLanguage = commaSeparatedLangCodes - } - - /** - * Saves whatever language codes are currently selected into a history array, - * which is then used to populate the language selector menu. - */ - savePostLanguageToHistory() { - // filter out duplicate `this.postLanguage` if exists, and prepend - // value to start of array - this.postLanguageHistory = [this.postLanguage] - .concat( - this.postLanguageHistory.filter( - commaSeparatedLangCodes => - commaSeparatedLangCodes !== this.postLanguage, - ), - ) - .slice(0, 6) - } - - getReadablePostLanguages() { - const all = this.postLanguages.map(code2 => { - const lang = LANGUAGES.find(l => l.code2 === code2) - return lang ? lang.name : code2 - }) - return all.join(', ') - } - // moderation // = @@ -599,17 +471,13 @@ export class PreferencesModel { } } - setPrimaryLanguage(lang: string) { - this.primaryLanguage = lang - } - getFeedTuners( feedType: 'home' | 'following' | 'author' | 'custom' | 'list' | 'likes', ) { if (feedType === 'custom') { return [ FeedTuner.dedupReposts, - FeedTuner.preferredLangOnly(this.contentLanguages), + FeedTuner.preferredLangOnly(getContentLanguages()), ] } if (feedType === 'list') { diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx new file mode 100644 index 0000000000..56c93f8126 --- /dev/null +++ b/src/state/preferences/index.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import {Provider as LanguagesProvider} from './languages' + +export {useLanguagePrefs, useSetLanguagePrefs} from './languages' + +export function Provider({children}: React.PropsWithChildren<{}>) { + return {children} +} diff --git a/src/state/preferences/languages.tsx b/src/state/preferences/languages.tsx new file mode 100644 index 0000000000..49b63550df --- /dev/null +++ b/src/state/preferences/languages.tsx @@ -0,0 +1,122 @@ +import React from 'react' +import * as persisted from '#/state/persisted' + +type SetStateCb = ( + v: persisted.Schema['languagePrefs'], +) => persisted.Schema['languagePrefs'] +type StateContext = persisted.Schema['languagePrefs'] +type SetContext = (fn: SetStateCb) => void + +const stateContext = React.createContext( + persisted.defaults.languagePrefs, +) +const setContext = React.createContext((_: SetStateCb) => {}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(persisted.get('languagePrefs')) + + const setStateWrapped = React.useCallback( + (fn: SetStateCb) => { + const v = fn(persisted.get('languagePrefs')) + setState(v) + persisted.write('languagePrefs', v) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(persisted.get('languagePrefs')) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export function useLanguagePrefs() { + return React.useContext(stateContext) +} + +export function useSetLanguagePrefs() { + return React.useContext(setContext) +} + +export function getContentLanguages() { + return persisted.get('languagePrefs').contentLanguages +} + +export function toggleContentLanguage( + state: StateContext, + setState: SetContext, + code2: string, +) { + if (state.contentLanguages.includes(code2)) { + setState(v => ({ + ...v, + contentLanguages: v.contentLanguages.filter(lang => lang !== code2), + })) + } else { + setState(v => ({ + ...v, + contentLanguages: v.contentLanguages.concat(code2), + })) + } +} + +export function toPostLanguages(postLanguage: string): string[] { + // filter out empty strings if exist + return postLanguage.split(',').filter(Boolean) +} + +export function hasPostLanguage(postLanguage: string, code2: string): boolean { + return toPostLanguages(postLanguage).includes(code2) +} + +export function togglePostLanguage( + state: StateContext, + setState: SetContext, + code2: string, +) { + if (hasPostLanguage(state.postLanguage, code2)) { + setState(v => ({ + ...v, + postLanguage: toPostLanguages(v.postLanguage) + .filter(lang => lang !== code2) + .join(','), + })) + } else { + // sort alphabetically for deterministic comparison in context menu + setState(v => ({ + ...v, + postLanguage: toPostLanguages(v.postLanguage) + .concat([code2]) + .sort((a, b) => a.localeCompare(b)) + .join(','), + })) + } +} + +/** + * Saves whatever language codes are currently selected into a history array, + * which is then used to populate the language selector menu. + */ +export function savePostLanguageToHistory(setState: SetContext) { + // filter out duplicate `this.postLanguage` if exists, and prepend + // value to start of array + setState(v => ({ + ...v, + postLanguageHistory: [v.postLanguage] + .concat( + v.postLanguageHistory.filter( + commaSeparatedLangCodes => commaSeparatedLangCodes !== v.postLanguage, + ), + ) + .slice(0, 6), + })) +} diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index a08992df42..4ebbfd666e 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -50,6 +50,12 @@ import {SelectLangBtn} from './select-language/SelectLangBtn' import {EmojiPickerButton} from './text-input/web/EmojiPicker.web' import {insertMentionAt} from 'lib/strings/mention-manip' import {useRequireAltTextEnabled} from '#/state/shell' +import { + useLanguagePrefs, + useSetLanguagePrefs, + toPostLanguages, + savePostLanguageToHistory, +} from '#/state/preferences/languages' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -63,6 +69,8 @@ export const ComposePost = observer(function ComposePost({ const {isDesktop, isMobile} = useWebMediaQueries() const store = useStores() const requireAltTextEnabled = useRequireAltTextEnabled() + const langPrefs = useLanguagePrefs() + const setLangPrefs = useSetLanguagePrefs() const textInput = useRef(null) const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true}) const [isProcessing, setIsProcessing] = useState(false) @@ -212,7 +220,7 @@ export const ComposePost = observer(function ComposePost({ labels, onStateChange: setProcessingState, knownHandles: autocompleteView.knownHandles, - langs: store.preferences.postLanguages, + langs: toPostLanguages(langPrefs.postLanguage), }) } catch (e: any) { if (extLink) { @@ -234,7 +242,7 @@ export const ComposePost = observer(function ComposePost({ if (!replyTo) { store.me.mainFeed.onPostCreated() } - store.preferences.savePostLanguageToHistory() + savePostLanguageToHistory(setLangPrefs) onPost?.() onClose() Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`) diff --git a/src/view/com/composer/select-language/SelectLangBtn.tsx b/src/view/com/composer/select-language/SelectLangBtn.tsx index 4faac37504..6465423879 100644 --- a/src/view/com/composer/select-language/SelectLangBtn.tsx +++ b/src/view/com/composer/select-language/SelectLangBtn.tsx @@ -15,10 +15,18 @@ import {usePalette} from 'lib/hooks/usePalette' import {useStores} from 'state/index' import {isNative} from 'platform/detection' import {codeToLanguageName} from '../../../../locale/helpers' +import { + useLanguagePrefs, + useSetLanguagePrefs, + toPostLanguages, + hasPostLanguage, +} from '#/state/preferences/languages' export const SelectLangBtn = observer(function SelectLangBtn() { const pal = usePalette('default') const store = useStores() + const langPrefs = useLanguagePrefs() + const setLangPrefs = useSetLanguagePrefs() const onPressMore = useCallback(async () => { if (isNative) { @@ -29,8 +37,7 @@ export const SelectLangBtn = observer(function SelectLangBtn() { store.shell.openModal({name: 'post-languages-settings'}) }, [store]) - const postLanguagesPref = store.preferences.postLanguages - const postLanguagePref = store.preferences.postLanguage + const postLanguagesPref = toPostLanguages(langPrefs.postLanguage) const items: DropdownItem[] = useMemo(() => { let arr: DropdownItemButton[] = [] @@ -49,13 +56,14 @@ export const SelectLangBtn = observer(function SelectLangBtn() { arr.push({ icon: - langCodes.every(code => store.preferences.hasPostLanguage(code)) && - langCodes.length === postLanguagesPref.length + langCodes.every(code => + hasPostLanguage(langPrefs.postLanguage, code), + ) && langCodes.length === postLanguagesPref.length ? ['fas', 'circle-dot'] : ['far', 'circle'], label: langName, onPress() { - store.preferences.setPostLanguage(commaSeparatedLangCodes) + setLangPrefs(v => ({...v, postLanguage: commaSeparatedLangCodes})) }, }) } @@ -65,11 +73,11 @@ export const SelectLangBtn = observer(function SelectLangBtn() { * Re-join here after sanitization bc postLanguageHistory is an array of * comma-separated strings too */ - add(postLanguagePref) + add(langPrefs.postLanguage) } // comma-separted strings of lang codes that have been used in the past - for (const lang of store.preferences.postLanguageHistory) { + for (const lang of langPrefs.postLanguageHistory) { add(lang) } @@ -82,7 +90,7 @@ export const SelectLangBtn = observer(function SelectLangBtn() { onPress: onPressMore, }, ] - }, [store.preferences, onPressMore, postLanguagePref, postLanguagesPref]) + }, [onPressMore, langPrefs, setLangPrefs, postLanguagesPref]) return ( { @@ -29,23 +36,23 @@ export function Component({}: {}) { // sort so that device & selected languages are on top, then alphabetically langs.sort((a, b) => { const hasA = - store.preferences.hasContentLanguage(a.code2) || + langPrefs.contentLanguages.includes(a.code2) || deviceLocales.includes(a.code2) const hasB = - store.preferences.hasContentLanguage(b.code2) || + langPrefs.contentLanguages.includes(b.code2) || deviceLocales.includes(b.code2) if (hasA === hasB) return a.name.localeCompare(b.name) if (hasA) return -1 return 1 }) return langs - }, [store]) + }, [langPrefs]) const onPress = React.useCallback( (code2: string) => { - store.preferences.toggleContentLanguage(code2) + toggleContentLanguage(langPrefs, setLangPrefs, code2) }, - [store], + [langPrefs, setLangPrefs], ) return ( diff --git a/src/view/com/modals/lang-settings/LanguageToggle.tsx b/src/view/com/modals/lang-settings/LanguageToggle.tsx index 187b46e8cb..86e38a4d29 100644 --- a/src/view/com/modals/lang-settings/LanguageToggle.tsx +++ b/src/view/com/modals/lang-settings/LanguageToggle.tsx @@ -3,7 +3,7 @@ import {StyleSheet} from 'react-native' import {usePalette} from 'lib/hooks/usePalette' import {observer} from 'mobx-react-lite' import {ToggleButton} from 'view/com/util/forms/ToggleButton' -import {useStores} from 'state/index' +import {useLanguagePrefs, toPostLanguages} from '#/state/preferences/languages' export const LanguageToggle = observer(function LanguageToggleImpl({ code2, @@ -17,17 +17,17 @@ export const LanguageToggle = observer(function LanguageToggleImpl({ langType: 'contentLanguages' | 'postLanguages' }) { const pal = usePalette('default') - const store = useStores() + const langPrefs = useLanguagePrefs() - const isSelected = store.preferences[langType].includes(code2) + const values = + langType === 'contentLanguages' + ? langPrefs.contentLanguages + : toPostLanguages(langPrefs.postLanguage) + const isSelected = values.includes(code2) // enforce a max of 3 selections for post languages let isDisabled = false - if ( - langType === 'postLanguages' && - store.preferences[langType].length >= 3 && - !isSelected - ) { + if (langType === 'postLanguages' && values.length >= 3 && !isSelected) { isDisabled = true } diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx index d74d884cc0..435fb9e1ac 100644 --- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx +++ b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx @@ -10,11 +10,19 @@ import {deviceLocales} from 'platform/detection' import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' import {ToggleButton} from 'view/com/util/forms/ToggleButton' +import { + useLanguagePrefs, + useSetLanguagePrefs, + hasPostLanguage, + togglePostLanguage, +} from '#/state/preferences/languages' export const snapPoints = ['100%'] export const Component = observer(function PostLanguagesSettingsImpl() { const store = useStores() + const langPrefs = useLanguagePrefs() + const setLangPrefs = useSetLanguagePrefs() const pal = usePalette('default') const {isMobile} = useWebMediaQueries() const onPressDone = React.useCallback(() => { @@ -30,23 +38,23 @@ export const Component = observer(function PostLanguagesSettingsImpl() { // sort so that device & selected languages are on top, then alphabetically langs.sort((a, b) => { const hasA = - store.preferences.hasPostLanguage(a.code2) || + hasPostLanguage(langPrefs.postLanguage, a.code2) || deviceLocales.includes(a.code2) const hasB = - store.preferences.hasPostLanguage(b.code2) || + hasPostLanguage(langPrefs.postLanguage, b.code2) || deviceLocales.includes(b.code2) if (hasA === hasB) return a.name.localeCompare(b.name) if (hasA) return -1 return 1 }) return langs - }, [store]) + }, [langPrefs]) const onPress = React.useCallback( (code2: string) => { - store.preferences.togglePostLanguage(code2) + togglePostLanguage(langPrefs, setLangPrefs, code2) }, - [store], + [langPrefs, setLangPrefs], ) return ( @@ -70,14 +78,11 @@ export const Component = observer(function PostLanguagesSettingsImpl() { {languages.map(lang => { - const isSelected = store.preferences.hasPostLanguage(lang.code2) + const isSelected = hasPostLanguage(langPrefs.postLanguage, lang.code2) // enforce a max of 3 selections for post languages let isDisabled = false - if ( - store.preferences.postLanguage.split(',').length >= 3 && - !isSelected - ) { + if (langPrefs.postLanguage.split(',').length >= 3 && !isSelected) { isDisabled = true } diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 9aec638ec2..b72121a375 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -38,6 +38,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {MAX_POST_LINES} from 'lib/constants' import {logger} from '#/logger' import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' +import {useLanguagePrefs} from '#/state/preferences' export const PostThreadItem = observer(function PostThreadItem({ item, @@ -54,6 +55,7 @@ export const PostThreadItem = observer(function PostThreadItem({ const store = useStores() const mutedThreads = useMutedThreads() const toggleThreadMute = useToggleThreadMute() + const langPrefs = useLanguagePrefs() const [deleted, setDeleted] = React.useState(false) const [limitLines, setLimitLines] = React.useState( countLines(item.richText?.text) >= MAX_POST_LINES, @@ -85,15 +87,15 @@ export const PostThreadItem = observer(function PostThreadItem({ const translatorUrl = getTranslatorLink( record?.text || '', - store.preferences.primaryLanguage, + langPrefs.primaryLanguage, ) const needsTranslation = useMemo( () => Boolean( - store.preferences.primaryLanguage && - !isPostInLanguage(item.post, [store.preferences.primaryLanguage]), + langPrefs.primaryLanguage && + !isPostInLanguage(item.post, [langPrefs.primaryLanguage]), ), - [item.post, store.preferences.primaryLanguage], + [item.post, langPrefs.primaryLanguage], ) const onPressReply = React.useCallback(() => { diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index db490333da..667584f68e 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -34,6 +34,7 @@ import {MAX_POST_LINES} from 'lib/constants' import {countLines} from 'lib/strings/helpers' import {logger} from '#/logger' import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' +import {useLanguagePrefs} from '#/state/preferences' export const Post = observer(function PostImpl({ view, @@ -109,6 +110,7 @@ const PostLoaded = observer(function PostLoadedImpl({ const store = useStores() const mutedThreads = useMutedThreads() const toggleThreadMute = useToggleThreadMute() + const langPrefs = useLanguagePrefs() const [limitLines, setLimitLines] = React.useState( countLines(item.richText?.text) >= MAX_POST_LINES, ) @@ -125,7 +127,7 @@ const PostLoaded = observer(function PostLoadedImpl({ const translatorUrl = getTranslatorLink( record?.text || '', - store.preferences.primaryLanguage, + langPrefs.primaryLanguage, ) const onPressReply = React.useCallback(() => { diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 772bb25614..527cbb76fc 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -34,6 +34,7 @@ import {MAX_POST_LINES} from 'lib/constants' import {countLines} from 'lib/strings/helpers' import {logger} from '#/logger' import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' +import {useLanguagePrefs} from '#/state/preferences' export const FeedItem = observer(function FeedItemImpl({ item, @@ -50,6 +51,7 @@ export const FeedItem = observer(function FeedItemImpl({ showReplyLine?: boolean }) { const store = useStores() + const langPrefs = useLanguagePrefs() const pal = usePalette('default') const mutedThreads = useMutedThreads() const toggleThreadMute = useToggleThreadMute() @@ -75,7 +77,7 @@ export const FeedItem = observer(function FeedItemImpl({ }, [record?.reply]) const translatorUrl = getTranslatorLink( record?.text || '', - store.preferences.primaryLanguage, + langPrefs.primaryLanguage, ) const onPressReply = React.useCallback(() => { diff --git a/src/view/screens/AppPasswords.tsx b/src/view/screens/AppPasswords.tsx index 74d293ef4a..b654055c47 100644 --- a/src/view/screens/AppPasswords.tsx +++ b/src/view/screens/AppPasswords.tsx @@ -17,6 +17,7 @@ import {useFocusEffect} from '@react-navigation/native' import {ViewHeader} from '../com/util/ViewHeader' import {CenteredView} from 'view/com/util/Views' import {useSetMinimalShellMode} from '#/state/shell' +import {useLanguagePrefs} from '#/state/preferences' type Props = NativeStackScreenProps export const AppPasswords = withAuthRequired( @@ -161,6 +162,7 @@ function AppPassword({ }) { const pal = usePalette('default') const store = useStores() + const {contentLanguages} = useLanguagePrefs() const onDelete = React.useCallback(async () => { store.shell.openModal({ @@ -174,8 +176,6 @@ function AppPassword({ }) }, [store, name]) - const {contentLanguages} = store.preferences - const primaryLocale = contentLanguages.length > 0 ? contentLanguages[0] : 'en-US' diff --git a/src/view/screens/LanguageSettings.tsx b/src/view/screens/LanguageSettings.tsx index a68a3b5e32..4cf178949e 100644 --- a/src/view/screens/LanguageSettings.tsx +++ b/src/view/screens/LanguageSettings.tsx @@ -19,6 +19,7 @@ import {useFocusEffect} from '@react-navigation/native' import {LANGUAGES} from 'lib/../locale/languages' import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import {useSetMinimalShellMode} from '#/state/shell' +import {useLanguagePrefs, useSetLanguagePrefs} from '#/state/preferences' type Props = NativeStackScreenProps @@ -27,6 +28,8 @@ export const LanguageSettingsScreen = observer(function LanguageSettingsImpl( ) { const pal = usePalette('default') const store = useStores() + const langPrefs = useLanguagePrefs() + const setLangPrefs = useSetLanguagePrefs() const {isTabletOrDesktop} = useWebMediaQueries() const {screen, track} = useAnalytics() const setMinimalShellMode = useSetMinimalShellMode() @@ -45,21 +48,23 @@ export const LanguageSettingsScreen = observer(function LanguageSettingsImpl( const onChangePrimaryLanguage = React.useCallback( (value: Parameters[0]) => { - store.preferences.setPrimaryLanguage(value) + if (langPrefs.primaryLanguage !== value) { + setLangPrefs(v => ({...v, primaryLanguage: value})) + } }, - [store.preferences], + [langPrefs, setLangPrefs], ) const myLanguages = React.useMemo(() => { return ( - store.preferences.contentLanguages + langPrefs.contentLanguages .map(lang => LANGUAGES.find(l => l.code2 === lang)) .filter(Boolean) // @ts-ignore .map(l => l.name) .join(', ') ) - }, [store.preferences.contentLanguages]) + }, [langPrefs.contentLanguages]) return ( Boolean(l.code2)).map(l => ({ label: l.name,