[APP-690] better handling of post languages language filtering (#893)
* add SelectLangBtn * memoized objects that are created to reduce re-creation on re-render * add langs when uploading post * only send the top 3 languages otherwise backend will throw error * mv ContentLanguagesSettings to folder * add post languages settings modal and state * fix typos * modify feed manip to also check langs label on post * Fix tests * Remove log * Update feed-manip.ts * Fix syntax errors * UI tuneups * Show the currently selected languages in the composer * fix linting * Use a bcp-47 matching function * Fix a duplicate language issue * Fix web * Dont include lang in prompt * Make select language btn an observer * Keep device languages on top of language selection UIs * Fix android build settings * Enforce a max of 3 languages in posts * Fix tests * Fix types --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
parent
9b19a95e63
commit
08804f265e
19 changed files with 525 additions and 176 deletions
|
@ -38,6 +38,7 @@ import {isDesktopWeb, isAndroid} from 'platform/detection'
|
|||
import {GalleryModel} from 'state/models/media/gallery'
|
||||
import {Gallery} from './photos/Gallery'
|
||||
import {MAX_GRAPHEME_LENGTH} from 'lib/constants'
|
||||
import {SelectLangBtn} from './select-language/SelectLangBtn'
|
||||
|
||||
type Props = ComposerOpts & {
|
||||
onClose: () => void
|
||||
|
@ -71,6 +72,13 @@ export const ComposePost = observer(function ComposePost({
|
|||
)
|
||||
|
||||
const insets = useSafeAreaInsets()
|
||||
const viewStyles = useMemo(
|
||||
() => ({
|
||||
paddingBottom: isAndroid ? insets.bottom : 0,
|
||||
paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15,
|
||||
}),
|
||||
[insets],
|
||||
)
|
||||
|
||||
// HACK
|
||||
// there's a bug with @mattermost/react-native-paste-input where if the input
|
||||
|
@ -87,6 +95,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
autocompleteView.setup()
|
||||
}, [autocompleteView])
|
||||
|
||||
// listen to escape key on desktop web
|
||||
const onEscape = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
|
@ -109,7 +118,6 @@ export const ComposePost = observer(function ComposePost({
|
|||
},
|
||||
[store, onClose],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isDesktopWeb) {
|
||||
window.addEventListener('keydown', onEscape)
|
||||
|
@ -157,6 +165,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
extLink: extLink,
|
||||
onStateChange: setProcessingState,
|
||||
knownHandles: autocompleteView.knownHandles,
|
||||
langs: store.preferences.postLanguages,
|
||||
})
|
||||
track('Create Post', {
|
||||
imageCount: gallery.size,
|
||||
|
@ -197,15 +206,13 @@ export const ComposePost = observer(function ComposePost({
|
|||
],
|
||||
)
|
||||
|
||||
const canPost = graphemeLength <= MAX_GRAPHEME_LENGTH
|
||||
const canPost = useMemo(
|
||||
() => graphemeLength <= MAX_GRAPHEME_LENGTH,
|
||||
[graphemeLength],
|
||||
)
|
||||
const selectTextInputPlaceholder = replyTo ? 'Write your reply' : `What's up?`
|
||||
|
||||
const selectTextInputPlaceholder = replyTo ? 'Write your reply' : "What's up?"
|
||||
|
||||
const canSelectImages = gallery.size < 4
|
||||
const viewStyles = {
|
||||
paddingBottom: isAndroid ? insets.bottom : 0,
|
||||
paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15,
|
||||
}
|
||||
const canSelectImages = useMemo(() => gallery.size < 4, [gallery.size])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
|
@ -352,6 +359,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
</>
|
||||
) : null}
|
||||
<View style={s.flex1} />
|
||||
<SelectLangBtn />
|
||||
<CharProgress count={graphemeLength} />
|
||||
</View>
|
||||
</View>
|
||||
|
|
56
src/view/com/composer/select-language/SelectLangBtn.tsx
Normal file
56
src/view/com/composer/select-language/SelectLangBtn.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React, {useCallback} from 'react'
|
||||
import {TouchableOpacity, StyleSheet, Keyboard} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useStores} from 'state/index'
|
||||
import {isNative} from 'platform/detection'
|
||||
|
||||
const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
|
||||
|
||||
export const SelectLangBtn = observer(function SelectLangBtn() {
|
||||
const pal = usePalette('default')
|
||||
const store = useStores()
|
||||
|
||||
const onPress = useCallback(async () => {
|
||||
if (isNative) {
|
||||
if (Keyboard.isVisible()) {
|
||||
Keyboard.dismiss()
|
||||
}
|
||||
}
|
||||
store.shell.openModal({name: 'post-languages-settings'})
|
||||
}, [store])
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID="selectLangBtn"
|
||||
onPress={onPress}
|
||||
style={styles.button}
|
||||
hitSlop={HITSLOP}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Language selection"
|
||||
accessibilityHint="Opens screen or modal to select language of post">
|
||||
{store.preferences.postLanguages.length > 0 ? (
|
||||
<Text type="lg-bold" style={pal.link}>
|
||||
{store.preferences.postLanguages.join(', ')}
|
||||
</Text>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon="language"
|
||||
style={pal.link as FontAwesomeIconStyle}
|
||||
size={26}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
})
|
|
@ -1,143 +0,0 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, Pressable, View} from 'react-native'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {ScrollView} from './util'
|
||||
import {useStores} from 'state/index'
|
||||
import {ToggleButton} from '../util/forms/ToggleButton'
|
||||
import {s, colors, gradients} from 'lib/styles'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {isDesktopWeb} from 'platform/detection'
|
||||
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../locale/languages'
|
||||
|
||||
export const snapPoints = ['100%']
|
||||
|
||||
export function Component({}: {}) {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const onPressDone = React.useCallback(() => {
|
||||
store.shell.closeModal()
|
||||
}, [store])
|
||||
|
||||
const languages = React.useMemo(() => {
|
||||
const langs = LANGUAGES.filter(
|
||||
lang =>
|
||||
!!lang.code2.trim() &&
|
||||
LANGUAGES_MAP_CODE2[lang.code2].code3 === lang.code3,
|
||||
)
|
||||
// sort so that selected languages are on top, then alphabetically
|
||||
langs.sort((a, b) => {
|
||||
const hasA = store.preferences.hasContentLanguage(a.code2)
|
||||
const hasB = store.preferences.hasContentLanguage(b.code2)
|
||||
if (hasA === hasB) return a.name.localeCompare(b.name)
|
||||
if (hasA) return -1
|
||||
return 1
|
||||
})
|
||||
return langs
|
||||
}, [store])
|
||||
|
||||
return (
|
||||
<View testID="contentLanguagesModal" style={[pal.view, styles.container]}>
|
||||
<Text style={[pal.text, styles.title]}>Content Languages</Text>
|
||||
<Text style={[pal.text, styles.description]}>
|
||||
Which languages would you like to see in the your feed? (Leave them all
|
||||
unchecked to see any language.)
|
||||
</Text>
|
||||
<ScrollView style={styles.scrollContainer}>
|
||||
{languages.map(lang => (
|
||||
<LanguageToggle
|
||||
key={lang.code2}
|
||||
code2={lang.code2}
|
||||
name={lang.name}
|
||||
/>
|
||||
))}
|
||||
<View style={styles.bottomSpacer} />
|
||||
</ScrollView>
|
||||
<View style={[styles.btnContainer, pal.borderDark]}>
|
||||
<Pressable
|
||||
testID="sendReportBtn"
|
||||
onPress={onPressDone}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Confirm content language settings"
|
||||
accessibilityHint="">
|
||||
<LinearGradient
|
||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
style={[styles.btn]}>
|
||||
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
|
||||
</LinearGradient>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const LanguageToggle = observer(
|
||||
({code2, name}: {code2: string; name: string}) => {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
store.preferences.toggleContentLanguage(code2)
|
||||
}, [store, code2])
|
||||
|
||||
return (
|
||||
<ToggleButton
|
||||
label={name}
|
||||
isSelected={store.preferences.contentLanguages.includes(code2)}
|
||||
onPress={onPress}
|
||||
style={[pal.border, styles.languageToggle]}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 24,
|
||||
marginBottom: 12,
|
||||
},
|
||||
description: {
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 10,
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
bottomSpacer: {
|
||||
height: isDesktopWeb ? 0 : 60,
|
||||
},
|
||||
btnContainer: {
|
||||
paddingTop: 10,
|
||||
paddingHorizontal: 10,
|
||||
paddingBottom: isDesktopWeb ? 0 : 40,
|
||||
borderTopWidth: isDesktopWeb ? 0 : 1,
|
||||
},
|
||||
|
||||
languageToggle: {
|
||||
borderTopWidth: 1,
|
||||
borderRadius: 0,
|
||||
paddingHorizontal: 0,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
borderRadius: 32,
|
||||
padding: 14,
|
||||
backgroundColor: colors.gray1,
|
||||
},
|
||||
})
|
|
@ -23,7 +23,8 @@ import * as WaitlistModal from './Waitlist'
|
|||
import * as InviteCodesModal from './InviteCodes'
|
||||
import * as AddAppPassword from './AddAppPasswords'
|
||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||
import * as ContentLanguagesSettingsModal from './ContentLanguagesSettings'
|
||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||
import * as PreferencesHomeFeed from './PreferencesHomeFeed'
|
||||
|
||||
const DEFAULT_SNAPPOINTS = ['90%']
|
||||
|
@ -106,6 +107,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
|||
} else if (activeModal?.name === 'content-languages-settings') {
|
||||
snapPoints = ContentLanguagesSettingsModal.snapPoints
|
||||
element = <ContentLanguagesSettingsModal.Component />
|
||||
} else if (activeModal?.name === 'post-languages-settings') {
|
||||
snapPoints = PostLanguagesSettingsModal.snapPoints
|
||||
element = <PostLanguagesSettingsModal.Component />
|
||||
} else if (activeModal?.name === 'preferences-home-feed') {
|
||||
snapPoints = PreferencesHomeFeed.snapPoints
|
||||
element = <PreferencesHomeFeed.Component />
|
||||
|
|
|
@ -23,7 +23,9 @@ import * as WaitlistModal from './Waitlist'
|
|||
import * as InviteCodesModal from './InviteCodes'
|
||||
import * as AddAppPassword from './AddAppPasswords'
|
||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||
import * as ContentLanguagesSettingsModal from './ContentLanguagesSettings'
|
||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||
|
||||
import * as PreferencesHomeFeed from './PreferencesHomeFeed'
|
||||
|
||||
export const ModalsContainer = observer(function ModalsContainer() {
|
||||
|
@ -94,6 +96,8 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||
element = <ContentFilteringSettingsModal.Component />
|
||||
} else if (modal.name === 'content-languages-settings') {
|
||||
element = <ContentLanguagesSettingsModal.Component />
|
||||
} else if (modal.name === 'post-languages-settings') {
|
||||
element = <PostLanguagesSettingsModal.Component />
|
||||
} else if (modal.name === 'alt-text-image') {
|
||||
element = <AltTextImageModal.Component {...modal} />
|
||||
} else if (modal.name === 'edit-image') {
|
||||
|
|
52
src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx
Normal file
52
src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, Text, View, Pressable} from 'react-native'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {s, colors, gradients} from 'lib/styles'
|
||||
import {isDesktopWeb} from 'platform/detection'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
|
||||
export const ConfirmLanguagesButton = ({
|
||||
onPress,
|
||||
extraText,
|
||||
}: {
|
||||
onPress: () => void
|
||||
extraText?: string
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
return (
|
||||
<View style={[styles.btnContainer, pal.borderDark]}>
|
||||
<Pressable
|
||||
testID="confirmContentLanguagesBtn"
|
||||
onPress={onPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Confirm content language settings"
|
||||
accessibilityHint="">
|
||||
<LinearGradient
|
||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
style={[styles.btn]}>
|
||||
<Text style={[s.white, s.bold, s.f18]}>Done{extraText}</Text>
|
||||
</LinearGradient>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
btnContainer: {
|
||||
paddingTop: 10,
|
||||
paddingHorizontal: 10,
|
||||
paddingBottom: isDesktopWeb ? 0 : 40,
|
||||
borderTopWidth: isDesktopWeb ? 0 : 1,
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
borderRadius: 32,
|
||||
padding: 14,
|
||||
backgroundColor: colors.gray1,
|
||||
},
|
||||
})
|
100
src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx
Normal file
100
src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
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'
|
||||
|
||||
export const snapPoints = ['100%']
|
||||
|
||||
export function Component({}: {}) {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const onPressDone = React.useCallback(() => {
|
||||
store.shell.closeModal()
|
||||
}, [store])
|
||||
|
||||
const languages = React.useMemo(() => {
|
||||
const langs = LANGUAGES.filter(
|
||||
lang =>
|
||||
!!lang.code2.trim() &&
|
||||
LANGUAGES_MAP_CODE2[lang.code2].code3 === lang.code3,
|
||||
)
|
||||
// sort so that device & selected languages are on top, then alphabetically
|
||||
langs.sort((a, b) => {
|
||||
const hasA =
|
||||
store.preferences.hasContentLanguage(a.code2) ||
|
||||
deviceLocales.includes(a.code2)
|
||||
const hasB =
|
||||
store.preferences.hasContentLanguage(b.code2) ||
|
||||
deviceLocales.includes(b.code2)
|
||||
if (hasA === hasB) return a.name.localeCompare(b.name)
|
||||
if (hasA) return -1
|
||||
return 1
|
||||
})
|
||||
return langs
|
||||
}, [store])
|
||||
|
||||
const onPress = React.useCallback(
|
||||
(code2: string) => {
|
||||
store.preferences.toggleContentLanguage(code2)
|
||||
},
|
||||
[store],
|
||||
)
|
||||
|
||||
return (
|
||||
<View testID="contentLanguagesModal" style={[pal.view, styles.container]}>
|
||||
<Text style={[pal.text, styles.title]}>Content Languages</Text>
|
||||
<Text style={[pal.text, styles.description]}>
|
||||
Which languages would you like to see in your algorithmic feeds?
|
||||
</Text>
|
||||
<Text style={[pal.textLight, styles.description]}>
|
||||
Leave them all unchecked to see any language.
|
||||
</Text>
|
||||
<ScrollView style={styles.scrollContainer}>
|
||||
{languages.map(lang => (
|
||||
<LanguageToggle
|
||||
key={lang.code2}
|
||||
code2={lang.code2}
|
||||
langType="contentLanguages"
|
||||
name={lang.name}
|
||||
onPress={() => {
|
||||
onPress(lang.code2)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<View style={styles.bottomSpacer} />
|
||||
</ScrollView>
|
||||
<ConfirmLanguagesButton onPress={onPressDone} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 24,
|
||||
marginBottom: 12,
|
||||
},
|
||||
description: {
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 10,
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
bottomSpacer: {
|
||||
height: isDesktopWeb ? 0 : 60,
|
||||
},
|
||||
})
|
56
src/view/com/modals/lang-settings/LanguageToggle.tsx
Normal file
56
src/view/com/modals/lang-settings/LanguageToggle.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React from 'react'
|
||||
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'
|
||||
|
||||
export const LanguageToggle = observer(
|
||||
({
|
||||
code2,
|
||||
name,
|
||||
onPress,
|
||||
langType,
|
||||
}: {
|
||||
code2: string
|
||||
name: string
|
||||
onPress: () => void
|
||||
langType: 'contentLanguages' | 'postLanguages'
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
const store = useStores()
|
||||
|
||||
const isSelected = store.preferences[langType].includes(code2)
|
||||
|
||||
// enforce a max of 3 selections for post languages
|
||||
let isDisabled = false
|
||||
if (
|
||||
langType === 'postLanguages' &&
|
||||
store.preferences[langType].length >= 3 &&
|
||||
!isSelected
|
||||
) {
|
||||
isDisabled = true
|
||||
}
|
||||
|
||||
return (
|
||||
<ToggleButton
|
||||
label={name}
|
||||
isSelected={isSelected}
|
||||
onPress={isDisabled ? undefined : onPress}
|
||||
style={[pal.border, styles.languageToggle, isDisabled && styles.dimmed]}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
languageToggle: {
|
||||
borderTopWidth: 1,
|
||||
borderRadius: 0,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
dimmed: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
})
|
97
src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
Normal file
97
src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
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'
|
||||
|
||||
export const snapPoints = ['100%']
|
||||
|
||||
export function Component({}: {}) {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const onPressDone = React.useCallback(() => {
|
||||
store.shell.closeModal()
|
||||
}, [store])
|
||||
|
||||
const languages = React.useMemo(() => {
|
||||
const langs = LANGUAGES.filter(
|
||||
lang =>
|
||||
!!lang.code2.trim() &&
|
||||
LANGUAGES_MAP_CODE2[lang.code2].code3 === lang.code3,
|
||||
)
|
||||
// sort so that device & selected languages are on top, then alphabetically
|
||||
langs.sort((a, b) => {
|
||||
const hasA =
|
||||
store.preferences.hasPostLanguage(a.code2) ||
|
||||
deviceLocales.includes(a.code2)
|
||||
const hasB =
|
||||
store.preferences.hasPostLanguage(b.code2) ||
|
||||
deviceLocales.includes(b.code2)
|
||||
if (hasA === hasB) return a.name.localeCompare(b.name)
|
||||
if (hasA) return -1
|
||||
return 1
|
||||
})
|
||||
return langs
|
||||
}, [store])
|
||||
|
||||
const onPress = React.useCallback(
|
||||
(code2: string) => {
|
||||
store.preferences.togglePostLanguage(code2)
|
||||
},
|
||||
[store],
|
||||
)
|
||||
|
||||
return (
|
||||
<View testID="postLanguagesModal" style={[pal.view, styles.container]}>
|
||||
<Text style={[pal.text, styles.title]}>Post Languages</Text>
|
||||
<Text style={[pal.text, styles.description]}>
|
||||
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)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<View style={styles.bottomSpacer} />
|
||||
</ScrollView>
|
||||
<ConfirmLanguagesButton onPress={onPressDone} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 24,
|
||||
marginBottom: 12,
|
||||
},
|
||||
description: {
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 10,
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
bottomSpacer: {
|
||||
height: isDesktopWeb ? 0 : 60,
|
||||
},
|
||||
})
|
|
@ -17,7 +17,7 @@ export function ToggleButton({
|
|||
label: string
|
||||
isSelected: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
onPress: () => void
|
||||
onPress?: () => void
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const circleStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue