about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-09-05 18:34:00 +0300
committerGitHub <noreply@github.com>2025-09-05 08:34:00 -0700
commitee3e08393882a9d72ae9cab5f765ed2885c5a98d (patch)
treea18396cf22b84b22073059922819e278890cc36a
parent0f089060d2596bf75f141d1d574f14952bca1066 (diff)
downloadvoidsky-ee3e08393882a9d72ae9cab5f765ed2885c5a98d.tar.zst
Restore quick language select (#8981)
* restore quick language select

* rm showCancel

* rm margin from thread button

* alf composer icons

* stop hiding keyboard

* use trans
-rw-r--r--src/state/persisted/schema.ts4
-rw-r--r--src/view/com/composer/Composer.tsx29
-rw-r--r--src/view/com/composer/select-language/PostLanguageSelect.tsx131
-rw-r--r--src/view/com/composer/select-language/PostLanguageSelectDialog.tsx (renamed from src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx)80
4 files changed, 154 insertions, 90 deletions
diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts
index f840081f3..11204f309 100644
--- a/src/state/persisted/schema.ts
+++ b/src/state/persisted/schema.ts
@@ -78,8 +78,8 @@ const schema = z.object({
     postLanguage: z.string(),
     /**
      * The user's post language history, used to pre-populate the post language
-     * selector in the composer. Within each value, multiple languages are
-     * separated by values.
+     * selector in the composer. Within each value, multiple languages are separated
+     * by commas.
      *
      * BCP-47 2-letter language codes without region.
      */
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 20f2549ad..61715a988 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -110,7 +110,6 @@ import {LabelsBtn} from '#/view/com/composer/labels/LabelsBtn'
 import {Gallery} from '#/view/com/composer/photos/Gallery'
 import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn'
 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'
 // TODO: Prevent naming components that coincide with RN primitives
 // 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 {atoms as a, native, useTheme, web} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
-import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
-import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
-import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
+import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji'
+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 * as Prompt from '#/components/Prompt'
 import * as Toast from '#/components/Toast'
 import {Text as NewText} from '#/components/Typography'
 import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
+import {PostLanguageSelect} from './select-language/PostLanguageSelect'
 import {
   type AssetType,
   SelectMediaButton,
@@ -941,7 +942,7 @@ let ComposerPost = React.memo(function ComposerPost({
                 })
               }
             }}>
-            <ButtonIcon icon={X} />
+            <ButtonIcon icon={XIcon} />
           </Button>
           <Prompt.Basic
             control={discardPromptControl}
@@ -1430,7 +1431,7 @@ function ComposerFooter({
                   variant="ghost"
                   shape="round"
                   color="primary">
-                  <EmojiSmile size="lg" />
+                  <EmojiSmileIcon size="lg" />
                 </Button>
               ) : null}
             </ToolbarWrapper>
@@ -1440,20 +1441,16 @@ function ComposerFooter({
       <View style={[a.flex_row, a.align_center, a.justify_between]}>
         {showAddButton && (
           <Button
-            label={_(msg`Add new post`)}
+            label={_(msg`Add another post to thread`)}
             onPress={onAddPost}
-            style={[a.p_sm, a.m_2xs]}
+            style={[a.p_sm]}
             variant="ghost"
             shape="round"
             color="primary">
-            <FontAwesomeIcon
-              icon="add"
-              size={20}
-              color={t.palette.primary_500}
-            />
+            <PlusIcon size="lg" />
           </Button>
         )}
-        <SelectPostLanguagesBtn />
+        <PostLanguageSelect />
         <CharProgress
           count={post.shortenedGraphemeLength}
           style={{width: 65}}
@@ -1753,7 +1750,7 @@ function ErrorBanner({
           t.atoms.bg_contrast_25,
         ]}>
         <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}]}>
             {error}
           </NewText>
@@ -1765,7 +1762,7 @@ function ErrorBanner({
             shape="round"
             style={[a.absolute, {top: 0, right: 0}]}
             onPress={onClearError}>
-            <ButtonIcon icon={X} />
+            <ButtonIcon icon={XIcon} />
           </Button>
         </View>
         {videoError && videoState.jobId && (
diff --git a/src/view/com/composer/select-language/PostLanguageSelect.tsx b/src/view/com/composer/select-language/PostLanguageSelect.tsx
new file mode 100644
index 000000000..6291e8422
--- /dev/null
+++ b/src/view/com/composer/select-language/PostLanguageSelect.tsx
@@ -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>
+  )
+}
diff --git a/src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx b/src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
index c8ecc2b89..1137415e6 100644
--- a/src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx
+++ b/src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
@@ -1,16 +1,13 @@
 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 {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {LANG_DROPDOWN_HITSLOP} from '#/lib/constants'
 import {languageName} from '#/locale/helpers'
-import {codeToLanguageName} from '#/locale/helpers'
 import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages'
 import {isNative, isWeb} from '#/platform/detection'
 import {
-  toPostLanguages,
   useLanguagePrefs,
   useLanguagePrefsApi,
 } from '#/state/preferences/languages'
@@ -21,75 +18,14 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import {SearchInput} from '#/components/forms/SearchInput'
 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 {Text} from '#/components/Typography'
 
-export function SelectPostLanguagesBtn() {
-  const {_} = useLingui()
-  const langPrefs = useLanguagePrefs()
-  const t = useTheme()
-  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}) {
+export function PostLanguageSelectDialog({
+  control,
+}: {
+  control: Dialog.DialogControlProps
+}) {
   const {height} = useWindowDimensions()
   const insets = useSafeAreaInsets()
 
@@ -104,13 +40,13 @@ function LanguageDialog({control}: {control: Dialog.DialogControlProps}) {
       nativeOptions={{minHeight: height - insets.top}}>
       <Dialog.Handle />
       <ErrorBoundary renderError={renderErrorBoundary}>
-        <PostLanguagesSettingsDialogInner />
+        <DialogInner />
       </ErrorBoundary>
     </Dialog.Outer>
   )
 }
 
-export function PostLanguagesSettingsDialogInner() {
+export function DialogInner() {
   const control = Dialog.useDialogContext()
   const [headerHeight, setHeaderHeight] = useState(0)