diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/state/models/ui/preferences.ts | 75 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 1 | ||||
-rw-r--r-- | src/view/com/composer/select-language/SelectLangBtn.tsx | 48 | ||||
-rw-r--r-- | src/view/com/modals/lang-settings/PostLanguagesSettings.tsx | 53 | ||||
-rw-r--r-- | src/view/com/util/forms/DropdownButton.tsx | 3 | ||||
-rw-r--r-- | src/view/index.ts | 2 |
6 files changed, 135 insertions, 47 deletions
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index 23668a3dc..e9ffe28c2 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -33,6 +33,9 @@ const LABEL_GROUPS = [ 'impersonation', ] const VISIBILITY_VALUES = ['show', 'warn', 'hide'] +const DEFAULT_LANG_CODES = (deviceLocales || []) + .concat(['en', 'ja', 'pt', 'de']) + .slice(0, 6) export class LabelPreferencesModel { nsfw: LabelPreference = 'hide' @@ -51,7 +54,8 @@ export class LabelPreferencesModel { export class PreferencesModel { adultContentEnabled = !isIOS contentLanguages: string[] = deviceLocales || [] - postLanguages: string[] = deviceLocales || [] + postLanguage: string = deviceLocales[0] || 'en' + postLanguageHistory: string[] = DEFAULT_LANG_CODES contentLabels = new LabelPreferencesModel() savedFeeds: string[] = [] pinnedFeeds: string[] = [] @@ -71,7 +75,8 @@ export class PreferencesModel { serialize() { return { contentLanguages: this.contentLanguages, - postLanguages: this.postLanguages, + postLanguage: this.postLanguage, + postLanguageHistory: this.postLanguageHistory, contentLabels: this.contentLabels, savedFeeds: this.savedFeeds, pinnedFeeds: this.pinnedFeeds, @@ -101,16 +106,23 @@ export class PreferencesModel { // default to the device languages this.contentLanguages = deviceLocales } - // check if post languages in preferences exist, otherwise default to device languages + 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, 'postLanguages') && - Array.isArray(v.postLanguages) && - typeof v.postLanguages.every(item => typeof item === 'string') + hasProp(v, 'postLanguageHistory') && + Array.isArray(v.postLanguageHistory) && + typeof v.postLanguageHistory.every(item => typeof item === 'string') ) { - this.postLanguages = v.postLanguages + this.postLanguageHistory = v.postLanguageHistory + .concat(DEFAULT_LANG_CODES) + .slice(0, 6) } else { - // default to the device languages - this.postLanguages = deviceLocales + // 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') { @@ -279,7 +291,8 @@ export class PreferencesModel { runInAction(() => { this.contentLabels = new LabelPreferencesModel() this.contentLanguages = deviceLocales - this.postLanguages = deviceLocales + this.postLanguage = deviceLocales ? deviceLocales.join(',') : 'en' + this.postLanguageHistory = DEFAULT_LANG_CODES this.savedFeeds = [] this.pinnedFeeds = [] }) @@ -305,20 +318,54 @@ export class PreferencesModel { } } + /** + * 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.postLanguages = this.postLanguages.filter(lang => lang !== code2) + this.postLanguage = this.postLanguages + .filter(lang => lang !== code2) + .join(',') } else { - this.postLanguages = this.postLanguages.concat([code2]) + // sort alphabetically for deterministic comparison in context menu + this.postLanguage = this.postLanguages + .concat([code2]) + .sort((a, b) => a.localeCompare(b)) + .join(',') } } - setPostLanguage(code2: string) { - this.postLanguages = [code2] + 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() { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index cb66cc909..f6308c394 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -212,6 +212,7 @@ export const ComposePost = observer(function ComposePost({ if (!replyTo) { store.me.mainFeed.onPostCreated() } + store.preferences.savePostLanguageToHistory() 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 5014b5409..4faac3750 100644 --- a/src/view/com/composer/select-language/SelectLangBtn.tsx +++ b/src/view/com/composer/select-language/SelectLangBtn.tsx @@ -15,7 +15,6 @@ import {usePalette} from 'lib/hooks/usePalette' import {useStores} from 'state/index' import {isNative} from 'platform/detection' import {codeToLanguageName} from '../../../../locale/helpers' -import {deviceLocales} from 'platform/detection' export const SelectLangBtn = observer(function SelectLangBtn() { const pal = usePalette('default') @@ -31,35 +30,48 @@ export const SelectLangBtn = observer(function SelectLangBtn() { }, [store]) const postLanguagesPref = store.preferences.postLanguages + const postLanguagePref = store.preferences.postLanguage const items: DropdownItem[] = useMemo(() => { let arr: DropdownItemButton[] = [] - const add = (langCode: string) => { - const langName = codeToLanguageName(langCode) + function add(commaSeparatedLangCodes: string) { + const langCodes = commaSeparatedLangCodes.split(',') + const langName = langCodes + .map(code => codeToLanguageName(code)) + .join(' + ') + + /* + * Filter out any duplicates + */ if (arr.find((item: DropdownItemButton) => item.label === langName)) { return } + arr.push({ - icon: store.preferences.hasPostLanguage(langCode) - ? ['fas', 'circle-check'] - : ['far', 'circle'], + icon: + langCodes.every(code => store.preferences.hasPostLanguage(code)) && + langCodes.length === postLanguagesPref.length + ? ['fas', 'circle-dot'] + : ['far', 'circle'], label: langName, onPress() { - store.preferences.setPostLanguage(langCode) + store.preferences.setPostLanguage(commaSeparatedLangCodes) }, }) } - for (const lang of postLanguagesPref) { - add(lang) + if (postLanguagesPref.length) { + /* + * Re-join here after sanitization bc postLanguageHistory is an array of + * comma-separated strings too + */ + add(postLanguagePref) } - for (const lang of deviceLocales) { + + // comma-separted strings of lang codes that have been used in the past + for (const lang of store.preferences.postLanguageHistory) { add(lang) } - add('en') // english - add('ja') // japanese - add('pt') // portugese - add('de') // german return [ {heading: true, label: 'Post language'}, @@ -70,7 +82,7 @@ export const SelectLangBtn = observer(function SelectLangBtn() { onPress: onPressMore, }, ] - }, [store.preferences, postLanguagesPref, onPressMore]) + }, [store.preferences, onPressMore, postLanguagePref, postLanguagesPref]) return ( <DropdownButton @@ -81,11 +93,9 @@ export const SelectLangBtn = observer(function SelectLangBtn() { style={styles.button} accessibilityLabel="Language selection" accessibilityHint=""> - {store.preferences.postLanguages.length > 0 ? ( + {postLanguagesPref.length > 0 ? ( <Text type="lg-bold" style={[pal.link, styles.label]} numberOfLines={1}> - {store.preferences.postLanguages - .map(lang => codeToLanguageName(lang)) - .join(', ')} + {postLanguagesPref.map(lang => codeToLanguageName(lang)).join(', ')} </Text> ) : ( <FontAwesomeIcon diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx index 3dc35e9ed..0f336e7bc 100644 --- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx +++ b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx @@ -1,17 +1,18 @@ import React from 'react' import {StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' import {ScrollView} from '../util' import {useStores} from 'state/index' import {Text} from '../../util/text/Text' import {usePalette} from 'lib/hooks/usePalette' import {isDesktopWeb, deviceLocales} from 'platform/detection' import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' -import {LanguageToggle} from './LanguageToggle' import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' +import {ToggleButton} from 'view/com/util/forms/ToggleButton' export const snapPoints = ['100%'] -export function Component({}: {}) { +export const Component = observer(() => { const store = useStores() const pal = usePalette('default') const onPressDone = React.useCallback(() => { @@ -53,23 +54,38 @@ export function Component({}: {}) { Which languages are used in this post? </Text> <ScrollView style={styles.scrollContainer}> - {languages.map(lang => ( - <LanguageToggle - key={lang.code2} - code2={lang.code2} - langType="postLanguages" - name={lang.name} - onPress={() => { - onPress(lang.code2) - }} - /> - ))} + {languages.map(lang => { + const isSelected = store.preferences.hasPostLanguage(lang.code2) + + // enforce a max of 3 selections for post languages + let isDisabled = false + if ( + store.preferences.postLanguage.split(',').length >= 3 && + !isSelected + ) { + isDisabled = true + } + + return ( + <ToggleButton + key={lang.code2} + label={lang.name} + isSelected={isSelected} + onPress={() => (isDisabled ? undefined : onPress(lang.code2))} + style={[ + pal.border, + styles.languageToggle, + isDisabled && styles.dimmed, + ]} + /> + ) + })} <View style={styles.bottomSpacer} /> </ScrollView> <ConfirmLanguagesButton onPress={onPressDone} /> </View> ) -} +}) const styles = StyleSheet.create({ container: { @@ -94,4 +110,13 @@ const styles = StyleSheet.create({ bottomSpacer: { height: isDesktopWeb ? 0 : 60, }, + languageToggle: { + borderTopWidth: 1, + borderRadius: 0, + paddingHorizontal: 6, + paddingVertical: 12, + }, + dimmed: { + opacity: 0.5, + }, }) diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index a1ee3d589..d5b254bb9 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -319,9 +319,12 @@ const styles = StyleSheet.create({ icon: { marginLeft: 2, marginRight: 8, + flexShrink: 0, }, label: { fontSize: 18, + flexShrink: 1, + flexGrow: 1, }, separator: { borderTopWidth: 1, diff --git a/src/view/index.ts b/src/view/index.ts index 4294508de..27655e900 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -29,6 +29,7 @@ import {faCircleCheck as farCircleCheck} from '@fortawesome/free-regular-svg-ico import {faCircleCheck} from '@fortawesome/free-solid-svg-icons/faCircleCheck' import {faCircleExclamation} from '@fortawesome/free-solid-svg-icons/faCircleExclamation' import {faCircleUser} from '@fortawesome/free-regular-svg-icons/faCircleUser' +import {faCircleDot} from '@fortawesome/free-solid-svg-icons/faCircleDot' import {faClone} from '@fortawesome/free-solid-svg-icons/faClone' import {faClone as farClone} from '@fortawesome/free-regular-svg-icons/faClone' import {faComment} from '@fortawesome/free-regular-svg-icons/faComment' @@ -122,6 +123,7 @@ export function setup() { farCircleCheck, faCircleExclamation, faCircleUser, + faCircleDot, faClone, farClone, faComment, |