Restore quick language select (#8981)
* restore quick language select * rm showCancel * rm margin from thread button * alf composer icons * stop hiding keyboard * use trans
This commit is contained in:
@@ -78,8 +78,8 @@ const schema = z.object({
|
|||||||
postLanguage: z.string(),
|
postLanguage: z.string(),
|
||||||
/**
|
/**
|
||||||
* The user's post language history, used to pre-populate the post language
|
* The user's post language history, used to pre-populate the post language
|
||||||
* selector in the composer. Within each value, multiple languages are
|
* selector in the composer. Within each value, multiple languages are separated
|
||||||
* separated by values.
|
* by commas.
|
||||||
*
|
*
|
||||||
* BCP-47 2-letter language codes without region.
|
* BCP-47 2-letter language codes without region.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ import {LabelsBtn} from '#/view/com/composer/labels/LabelsBtn'
|
|||||||
import {Gallery} from '#/view/com/composer/photos/Gallery'
|
import {Gallery} from '#/view/com/composer/photos/Gallery'
|
||||||
import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn'
|
import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn'
|
||||||
import {SelectGifBtn} from '#/view/com/composer/photos/SelectGifBtn'
|
import {SelectGifBtn} from '#/view/com/composer/photos/SelectGifBtn'
|
||||||
import {SelectPostLanguagesBtn} from '#/view/com/composer/select-language/SelectPostLanguagesDialog'
|
|
||||||
import {SuggestedLanguage} from '#/view/com/composer/select-language/SuggestedLanguage'
|
import {SuggestedLanguage} from '#/view/com/composer/select-language/SuggestedLanguage'
|
||||||
// TODO: Prevent naming components that coincide with RN primitives
|
// TODO: Prevent naming components that coincide with RN primitives
|
||||||
// due to linting false positives
|
// due to linting false positives
|
||||||
@@ -123,14 +122,16 @@ import {Text} from '#/view/com/util/text/Text'
|
|||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, native, useTheme, web} from '#/alf'
|
import {atoms as a, native, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
|
||||||
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||||
|
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||||
import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
|
import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text as NewText} from '#/components/Typography'
|
import {Text as NewText} from '#/components/Typography'
|
||||||
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
||||||
|
import {PostLanguageSelect} from './select-language/PostLanguageSelect'
|
||||||
import {
|
import {
|
||||||
type AssetType,
|
type AssetType,
|
||||||
SelectMediaButton,
|
SelectMediaButton,
|
||||||
@@ -941,7 +942,7 @@ let ComposerPost = React.memo(function ComposerPost({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<ButtonIcon icon={X} />
|
<ButtonIcon icon={XIcon} />
|
||||||
</Button>
|
</Button>
|
||||||
<Prompt.Basic
|
<Prompt.Basic
|
||||||
control={discardPromptControl}
|
control={discardPromptControl}
|
||||||
@@ -1430,7 +1431,7 @@ function ComposerFooter({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
shape="round"
|
shape="round"
|
||||||
color="primary">
|
color="primary">
|
||||||
<EmojiSmile size="lg" />
|
<EmojiSmileIcon size="lg" />
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</ToolbarWrapper>
|
</ToolbarWrapper>
|
||||||
@@ -1440,20 +1441,16 @@ function ComposerFooter({
|
|||||||
<View style={[a.flex_row, a.align_center, a.justify_between]}>
|
<View style={[a.flex_row, a.align_center, a.justify_between]}>
|
||||||
{showAddButton && (
|
{showAddButton && (
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Add new post`)}
|
label={_(msg`Add another post to thread`)}
|
||||||
onPress={onAddPost}
|
onPress={onAddPost}
|
||||||
style={[a.p_sm, a.m_2xs]}
|
style={[a.p_sm]}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
shape="round"
|
shape="round"
|
||||||
color="primary">
|
color="primary">
|
||||||
<FontAwesomeIcon
|
<PlusIcon size="lg" />
|
||||||
icon="add"
|
|
||||||
size={20}
|
|
||||||
color={t.palette.primary_500}
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<SelectPostLanguagesBtn />
|
<PostLanguageSelect />
|
||||||
<CharProgress
|
<CharProgress
|
||||||
count={post.shortenedGraphemeLength}
|
count={post.shortenedGraphemeLength}
|
||||||
style={{width: 65}}
|
style={{width: 65}}
|
||||||
@@ -1753,7 +1750,7 @@ function ErrorBanner({
|
|||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_25,
|
||||||
]}>
|
]}>
|
||||||
<View style={[a.relative, a.flex_row, a.gap_sm, {paddingRight: 48}]}>
|
<View style={[a.relative, a.flex_row, a.gap_sm, {paddingRight: 48}]}>
|
||||||
<CircleInfo fill={t.palette.negative_400} />
|
<CircleInfoIcon fill={t.palette.negative_400} />
|
||||||
<NewText style={[a.flex_1, a.leading_snug, {paddingTop: 1}]}>
|
<NewText style={[a.flex_1, a.leading_snug, {paddingTop: 1}]}>
|
||||||
{error}
|
{error}
|
||||||
</NewText>
|
</NewText>
|
||||||
@@ -1765,7 +1762,7 @@ function ErrorBanner({
|
|||||||
shape="round"
|
shape="round"
|
||||||
style={[a.absolute, {top: 0, right: 0}]}
|
style={[a.absolute, {top: 0, right: 0}]}
|
||||||
onPress={onClearError}>
|
onPress={onClearError}>
|
||||||
<ButtonIcon icon={X} />
|
<ButtonIcon icon={XIcon} />
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
{videoError && videoState.jobId && (
|
{videoError && videoState.jobId && (
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {LANG_DROPDOWN_HITSLOP} from '#/lib/constants'
|
||||||
|
import {codeToLanguageName} from '#/locale/helpers'
|
||||||
|
import {
|
||||||
|
toPostLanguages,
|
||||||
|
useLanguagePrefs,
|
||||||
|
useLanguagePrefsApi,
|
||||||
|
} from '#/state/preferences/languages'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button, type ButtonProps} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron'
|
||||||
|
import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
|
||||||
|
import * as Menu from '#/components/Menu'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {PostLanguageSelectDialog} from './PostLanguageSelectDialog'
|
||||||
|
|
||||||
|
export function PostLanguageSelect() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const langPrefs = useLanguagePrefs()
|
||||||
|
const setLangPrefs = useLanguagePrefsApi()
|
||||||
|
const languageDialogControl = Dialog.useDialogControl()
|
||||||
|
|
||||||
|
const dedupedHistory = Array.from(
|
||||||
|
new Set([...langPrefs.postLanguageHistory, langPrefs.postLanguage]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
dedupedHistory.length === 1 &&
|
||||||
|
dedupedHistory[0] === langPrefs.postLanguage
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LanguageBtn onPress={languageDialogControl.open} />
|
||||||
|
<PostLanguageSelectDialog control={languageDialogControl} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Menu.Root>
|
||||||
|
<Menu.Trigger label={_(msg`Select post language`)}>
|
||||||
|
{({props}) => <LanguageBtn {...props} />}
|
||||||
|
</Menu.Trigger>
|
||||||
|
<Menu.Outer>
|
||||||
|
<Menu.Group>
|
||||||
|
{dedupedHistory.map(historyItem => {
|
||||||
|
const langCodes = historyItem.split(',')
|
||||||
|
const langName = langCodes
|
||||||
|
.map(code => codeToLanguageName(code, langPrefs.appLanguage))
|
||||||
|
.join(' + ')
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
key={historyItem}
|
||||||
|
label={_(msg`Select ${langName}`)}
|
||||||
|
onPress={() => setLangPrefs.setPostLanguage(historyItem)}>
|
||||||
|
<Menu.ItemText>{langName}</Menu.ItemText>
|
||||||
|
<Menu.ItemRadio
|
||||||
|
selected={historyItem === langPrefs.postLanguage}
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Menu.Group>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item
|
||||||
|
label={_(msg`More languages...`)}
|
||||||
|
onPress={languageDialogControl.open}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>More languages...</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={ChevronRightIcon} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Outer>
|
||||||
|
</Menu.Root>
|
||||||
|
|
||||||
|
<PostLanguageSelectDialog control={languageDialogControl} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LanguageBtn(props: Omit<ButtonProps, 'label' | 'children'>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const langPrefs = useLanguagePrefs()
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
const postLanguagesPref = toPostLanguages(langPrefs.postLanguage)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
testID="selectLangBtn"
|
||||||
|
size="small"
|
||||||
|
hitSlop={LANG_DROPDOWN_HITSLOP}
|
||||||
|
label={_(
|
||||||
|
msg({
|
||||||
|
message: `Post language selection`,
|
||||||
|
comment: `Accessibility label for button that opens dialog to choose post language settings`,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
accessibilityHint={_(msg`Opens post language settings`)}
|
||||||
|
style={[a.mr_xs]}
|
||||||
|
{...props}>
|
||||||
|
{({pressed, hovered}) => {
|
||||||
|
const color =
|
||||||
|
pressed || hovered ? t.palette.primary_300 : t.palette.primary_500
|
||||||
|
if (postLanguagesPref.length > 0) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
{color},
|
||||||
|
a.font_bold,
|
||||||
|
a.text_sm,
|
||||||
|
a.leading_snug,
|
||||||
|
{maxWidth: 100},
|
||||||
|
]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
{postLanguagesPref
|
||||||
|
.map(lang => codeToLanguageName(lang, langPrefs.appLanguage))
|
||||||
|
.join(', ')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <GlobeIcon size="xs" style={{color}} />
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
+8
-72
@@ -1,16 +1,13 @@
|
|||||||
import {useCallback, useMemo, useState} from 'react'
|
import {useCallback, useMemo, useState} from 'react'
|
||||||
import {Keyboard, useWindowDimensions, View} from 'react-native'
|
import {useWindowDimensions, View} from 'react-native'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {LANG_DROPDOWN_HITSLOP} from '#/lib/constants'
|
|
||||||
import {languageName} from '#/locale/helpers'
|
import {languageName} from '#/locale/helpers'
|
||||||
import {codeToLanguageName} from '#/locale/helpers'
|
|
||||||
import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages'
|
import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {
|
import {
|
||||||
toPostLanguages,
|
|
||||||
useLanguagePrefs,
|
useLanguagePrefs,
|
||||||
useLanguagePrefsApi,
|
useLanguagePrefsApi,
|
||||||
} from '#/state/preferences/languages'
|
} from '#/state/preferences/languages'
|
||||||
@@ -21,75 +18,14 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {SearchInput} from '#/components/forms/SearchInput'
|
import {SearchInput} from '#/components/forms/SearchInput'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
|
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export function SelectPostLanguagesBtn() {
|
export function PostLanguageSelectDialog({
|
||||||
const {_} = useLingui()
|
control,
|
||||||
const langPrefs = useLanguagePrefs()
|
}: {
|
||||||
const t = useTheme()
|
control: Dialog.DialogControlProps
|
||||||
const control = Dialog.useDialogControl()
|
}) {
|
||||||
|
|
||||||
const onPressMore = useCallback(async () => {
|
|
||||||
if (isNative) {
|
|
||||||
if (Keyboard.isVisible()) {
|
|
||||||
Keyboard.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
control.open()
|
|
||||||
}, [control])
|
|
||||||
|
|
||||||
const postLanguagesPref = toPostLanguages(langPrefs.postLanguage)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
testID="selectLangBtn"
|
|
||||||
onPress={onPressMore}
|
|
||||||
size="small"
|
|
||||||
hitSlop={LANG_DROPDOWN_HITSLOP}
|
|
||||||
label={_(
|
|
||||||
msg({
|
|
||||||
message: `Post language selection`,
|
|
||||||
comment: `Accessibility label for button that opens dialog to choose post language settings`,
|
|
||||||
}),
|
|
||||||
)}
|
|
||||||
accessibilityHint={_(msg`Opens post language settings`)}
|
|
||||||
style={[a.mx_md]}>
|
|
||||||
{({pressed, hovered, focused}) => {
|
|
||||||
const color =
|
|
||||||
pressed || hovered || focused
|
|
||||||
? t.palette.primary_300
|
|
||||||
: t.palette.primary_500
|
|
||||||
if (postLanguagesPref.length > 0) {
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
{color},
|
|
||||||
a.font_bold,
|
|
||||||
a.text_sm,
|
|
||||||
a.leading_snug,
|
|
||||||
{maxWidth: 100},
|
|
||||||
]}
|
|
||||||
numberOfLines={1}>
|
|
||||||
{postLanguagesPref
|
|
||||||
.map(lang => codeToLanguageName(lang, langPrefs.appLanguage))
|
|
||||||
.join(', ')}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return <GlobeIcon size="xs" style={{color}} />
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<LanguageDialog control={control} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LanguageDialog({control}: {control: Dialog.DialogControlProps}) {
|
|
||||||
const {height} = useWindowDimensions()
|
const {height} = useWindowDimensions()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
@@ -104,13 +40,13 @@ function LanguageDialog({control}: {control: Dialog.DialogControlProps}) {
|
|||||||
nativeOptions={{minHeight: height - insets.top}}>
|
nativeOptions={{minHeight: height - insets.top}}>
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<ErrorBoundary renderError={renderErrorBoundary}>
|
<ErrorBoundary renderError={renderErrorBoundary}>
|
||||||
<PostLanguagesSettingsDialogInner />
|
<DialogInner />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostLanguagesSettingsDialogInner() {
|
export function DialogInner() {
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
const [headerHeight, setHeaderHeight] = useState(0)
|
const [headerHeight, setHeaderHeight] = useState(0)
|
||||||
|
|
||||||
Reference in New Issue
Block a user