about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/composer/Composer.tsx9
-rw-r--r--src/view/com/composer/char-progress/CharProgress.tsx2
-rw-r--r--src/view/com/composer/select-language/SelectLangBtn.tsx78
-rw-r--r--src/view/com/post/Post.tsx29
-rw-r--r--src/view/com/util/forms/DropdownButton.tsx44
5 files changed, 136 insertions, 26 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index c6a9ecd4a..f2e3cbd63 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -16,6 +16,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {RichText} from '@atproto/api'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
+import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
 import {ExternalEmbed} from './ExternalEmbed'
 import {Text} from '../util/text/Text'
 import * as Toast from '../util/Toast'
@@ -35,7 +36,7 @@ import {OpenCameraBtn} from './photos/OpenCameraBtn'
 import {usePalette} from 'lib/hooks/usePalette'
 import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
 import {useExternalLinkFetch} from './useExternalLinkFetch'
-import {isDesktopWeb, isAndroid} from 'platform/detection'
+import {isDesktopWeb, isAndroid, isIOS} from 'platform/detection'
 import {GalleryModel} from 'state/models/media/gallery'
 import {Gallery} from './photos/Gallery'
 import {MAX_GRAPHEME_LENGTH} from 'lib/constants'
@@ -55,6 +56,7 @@ export const ComposePost = observer(function ComposePost({
   const pal = usePalette('default')
   const store = useStores()
   const textInput = useRef<TextInputRef>(null)
+  const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true})
   const [isProcessing, setIsProcessing] = useState(false)
   const [processingState, setProcessingState] = useState('')
   const [error, setError] = useState('')
@@ -75,10 +77,11 @@ export const ComposePost = observer(function ComposePost({
   const insets = useSafeAreaInsets()
   const viewStyles = useMemo(
     () => ({
-      paddingBottom: isAndroid ? insets.bottom : 0,
+      paddingBottom:
+        isAndroid || (isIOS && !isKeyboardVisible) ? insets.bottom : 0,
       paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15,
     }),
-    [insets],
+    [insets, isKeyboardVisible],
   )
 
   // HACK
diff --git a/src/view/com/composer/char-progress/CharProgress.tsx b/src/view/com/composer/char-progress/CharProgress.tsx
index 6b3b98e47..a3fa78a59 100644
--- a/src/view/com/composer/char-progress/CharProgress.tsx
+++ b/src/view/com/composer/char-progress/CharProgress.tsx
@@ -17,7 +17,7 @@ export function CharProgress({count}: {count: number}) {
   const circleColor = count > DANGER_LENGTH ? '#e60000' : pal.colors.link
   return (
     <>
-      <Text style={[s.mr10, {color: textColor}]}>
+      <Text style={[s.mr10, s.tabularNum, {color: textColor}]}>
         {MAX_GRAPHEME_LENGTH - count}
       </Text>
       <View>
diff --git a/src/view/com/composer/select-language/SelectLangBtn.tsx b/src/view/com/composer/select-language/SelectLangBtn.tsx
index 8c55e1c91..5014b5409 100644
--- a/src/view/com/composer/select-language/SelectLangBtn.tsx
+++ b/src/view/com/composer/select-language/SelectLangBtn.tsx
@@ -1,22 +1,27 @@
-import React, {useCallback} from 'react'
-import {TouchableOpacity, StyleSheet, Keyboard} from 'react-native'
+import React, {useCallback, useMemo} from 'react'
+import {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 {
+  DropdownButton,
+  DropdownItem,
+  DropdownItemButton,
+} from 'view/com/util/forms/DropdownButton'
 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}
+import {codeToLanguageName} from '../../../../locale/helpers'
+import {deviceLocales} from 'platform/detection'
 
 export const SelectLangBtn = observer(function SelectLangBtn() {
   const pal = usePalette('default')
   const store = useStores()
 
-  const onPress = useCallback(async () => {
+  const onPressMore = useCallback(async () => {
     if (isNative) {
       if (Keyboard.isVisible()) {
         Keyboard.dismiss()
@@ -25,18 +30,62 @@ export const SelectLangBtn = observer(function SelectLangBtn() {
     store.shell.openModal({name: 'post-languages-settings'})
   }, [store])
 
+  const postLanguagesPref = store.preferences.postLanguages
+  const items: DropdownItem[] = useMemo(() => {
+    let arr: DropdownItemButton[] = []
+
+    const add = (langCode: string) => {
+      const langName = codeToLanguageName(langCode)
+      if (arr.find((item: DropdownItemButton) => item.label === langName)) {
+        return
+      }
+      arr.push({
+        icon: store.preferences.hasPostLanguage(langCode)
+          ? ['fas', 'circle-check']
+          : ['far', 'circle'],
+        label: langName,
+        onPress() {
+          store.preferences.setPostLanguage(langCode)
+        },
+      })
+    }
+
+    for (const lang of postLanguagesPref) {
+      add(lang)
+    }
+    for (const lang of deviceLocales) {
+      add(lang)
+    }
+    add('en') // english
+    add('ja') // japanese
+    add('pt') // portugese
+    add('de') // german
+
+    return [
+      {heading: true, label: 'Post language'},
+      ...arr.slice(0, 6),
+      {sep: true},
+      {
+        label: 'Other...',
+        onPress: onPressMore,
+      },
+    ]
+  }, [store.preferences, postLanguagesPref, onPressMore])
+
   return (
-    <TouchableOpacity
+    <DropdownButton
+      type="bare"
       testID="selectLangBtn"
-      onPress={onPress}
+      items={items}
+      openUpwards
       style={styles.button}
-      hitSlop={HITSLOP}
-      accessibilityRole="button"
       accessibilityLabel="Language selection"
-      accessibilityHint="Opens screen or modal to select language of post">
+      accessibilityHint="">
       {store.preferences.postLanguages.length > 0 ? (
-        <Text type="lg-bold" style={pal.link}>
-          {store.preferences.postLanguages.join(', ')}
+        <Text type="lg-bold" style={[pal.link, styles.label]} numberOfLines={1}>
+          {store.preferences.postLanguages
+            .map(lang => codeToLanguageName(lang))
+            .join(', ')}
         </Text>
       ) : (
         <FontAwesomeIcon
@@ -45,7 +94,7 @@ export const SelectLangBtn = observer(function SelectLangBtn() {
           size={26}
         />
       )}
-    </TouchableOpacity>
+    </DropdownButton>
   )
 })
 
@@ -53,4 +102,7 @@ const styles = StyleSheet.create({
   button: {
     paddingHorizontal: 15,
   },
+  label: {
+    maxWidth: 100,
+  },
 })
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 12ab0e901..c380c9743 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect, useState} from 'react'
+import React, {useEffect, useState, useMemo} from 'react'
 import {
   ActivityIndicator,
   Linking,
@@ -29,7 +29,7 @@ import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
-import {getTranslatorLink} from '../../../locale/helpers'
+import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
 
 export const Post = observer(function Post({
   uri,
@@ -134,6 +134,16 @@ const PostLoaded = observer(
       const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri)
       replyAuthorDid = urip.hostname
     }
+
+    const primaryLanguage = store.preferences.contentLanguages[0] || 'en'
+    const translatorUrl = getTranslatorLink(primaryLanguage, record?.text || '')
+    const needsTranslation = useMemo(
+      () =>
+        store.preferences.contentLanguages.length > 0 &&
+        !isPostInLanguage(item.post, store.preferences.contentLanguages),
+      [item.post, store.preferences.contentLanguages],
+    )
+
     const onPressReply = React.useCallback(() => {
       store.shell.openComposer({
         replyTo: {
@@ -166,9 +176,6 @@ const PostLoaded = observer(
       Toast.show('Copied to clipboard')
     }, [record])
 
-    const primaryLanguage = store.preferences.contentLanguages[0] || 'en'
-    const translatorUrl = getTranslatorLink(primaryLanguage, record?.text || '')
-
     const onOpenTranslate = React.useCallback(() => {
       Linking.openURL(translatorUrl)
     }, [translatorUrl])
@@ -263,6 +270,15 @@ const PostLoaded = observer(
               <ImageHider moderation={item.moderation.list} style={s.mb10}>
                 <PostEmbeds embed={item.post.embed} style={s.mb10} />
               </ImageHider>
+              {needsTranslation && (
+                <View style={[pal.borderDark, styles.translateLink]}>
+                  <Link href={translatorUrl} title="Translate">
+                    <Text type="sm" style={pal.link}>
+                      Translate this post
+                    </Text>
+                  </Link>
+                </View>
+              )}
             </ContentHider>
             <PostCtrls
               itemUri={itemUri}
@@ -320,6 +336,9 @@ const styles = StyleSheet.create({
     flexWrap: 'wrap',
     paddingBottom: 8,
   },
+  translateLink: {
+    marginBottom: 12,
+  },
   replyLine: {
     position: 'absolute',
     left: 36,
diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx
index ad216d97e..046610b29 100644
--- a/src/view/com/util/forms/DropdownButton.tsx
+++ b/src/view/com/util/forms/DropdownButton.tsx
@@ -24,6 +24,7 @@ import {shareUrl} from 'lib/sharing'
 const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
 const ESTIMATED_BTN_HEIGHT = 50
 const ESTIMATED_SEP_HEIGHT = 16
+const ESTIMATED_HEADING_HEIGHT = 60
 
 export interface DropdownItemButton {
   testID?: string
@@ -34,7 +35,14 @@ export interface DropdownItemButton {
 export interface DropdownItemSeparator {
   sep: true
 }
-export type DropdownItem = DropdownItemButton | DropdownItemSeparator
+export interface DropdownItemHeading {
+  heading: true
+  label: string
+}
+export type DropdownItem =
+  | DropdownItemButton
+  | DropdownItemSeparator
+  | DropdownItemHeading
 type MaybeDropdownItem = DropdownItem | false | undefined
 
 export type DropdownButtonType = ButtonType | 'bare'
@@ -48,6 +56,7 @@ interface DropdownButtonProps {
   menuWidth?: number
   children?: React.ReactNode
   openToRight?: boolean
+  openUpwards?: boolean
   rightOffset?: number
   bottomOffset?: number
   accessibilityLabel?: string
@@ -63,6 +72,7 @@ export function DropdownButton({
   menuWidth,
   children,
   openToRight = false,
+  openUpwards = false,
   rightOffset = 0,
   bottomOffset = 0,
   accessibilityLabel,
@@ -91,13 +101,15 @@ export function DropdownButton({
             estimatedMenuHeight += ESTIMATED_SEP_HEIGHT
           } else if (item && isBtn(item)) {
             estimatedMenuHeight += ESTIMATED_BTN_HEIGHT
+          } else if (item && isHeading(item)) {
+            estimatedMenuHeight += ESTIMATED_HEADING_HEIGHT
           }
         }
         const newX = openToRight
           ? pageX + width + rightOffset
           : pageX + width - menuWidth
         let newY = pageY + height + bottomOffset
-        if (newY + estimatedMenuHeight > winHeight) {
+        if (openUpwards || newY + estimatedMenuHeight > winHeight) {
           newY -= estimatedMenuHeight
         }
         createDropdownMenu(
@@ -357,6 +369,14 @@ const DropdownItems = ({
             return (
               <View key={index} style={[styles.separator, separatorColor]} />
             )
+          } else if (isHeading(item)) {
+            return (
+              <View style={[styles.heading, pal.border]} key={index}>
+                <Text style={[pal.text, styles.headingLabel]}>
+                  {item.label}
+                </Text>
+              </View>
+            )
           }
           return null
         })}
@@ -368,8 +388,11 @@ const DropdownItems = ({
 function isSep(item: DropdownItem): item is DropdownItemSeparator {
   return 'sep' in item && item.sep
 }
+function isHeading(item: DropdownItem): item is DropdownItemHeading {
+  return 'heading' in item && item.heading
+}
 function isBtn(item: DropdownItem): item is DropdownItemButton {
-  return !isSep(item)
+  return !isSep(item) && !isHeading(item)
 }
 
 const styles = StyleSheet.create({
@@ -403,7 +426,7 @@ const styles = StyleSheet.create({
     paddingTop: 12,
   },
   icon: {
-    marginLeft: 6,
+    marginLeft: 2,
     marginRight: 8,
   },
   label: {
@@ -413,4 +436,17 @@ const styles = StyleSheet.create({
     borderTopWidth: 1,
     marginVertical: 8,
   },
+  heading: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    paddingVertical: 10,
+    paddingLeft: 15,
+    paddingRight: 20,
+    borderBottomWidth: 1,
+    marginBottom: 6,
+  },
+  headingLabel: {
+    fontSize: 18,
+    fontWeight: '500',
+  },
 })