about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/composer/Composer.tsx71
-rw-r--r--src/view/com/composer/photos/OpenCameraBtn.tsx59
-rw-r--r--src/view/com/composer/photos/SelectGifBtn.tsx53
-rw-r--r--src/view/com/composer/photos/SelectPhotoBtn.tsx55
-rw-r--r--src/view/screens/Storybook/Buttons.tsx2
-rw-r--r--src/view/screens/Storybook/Icons.tsx6
6 files changed, 154 insertions, 92 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index f90bdbee2..f0f630dd4 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -13,7 +13,6 @@ import {
   KeyboardAvoidingView,
   LayoutAnimation,
   Platform,
-  Pressable,
   ScrollView,
   StyleSheet,
   TouchableOpacity,
@@ -27,6 +26,7 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {observer} from 'mobx-react-lite'
 
+import {LikelyType} from '#/lib/link-meta/link-meta'
 import {logEvent} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
 import {emitPostCreated} from '#/state/events'
@@ -37,6 +37,7 @@ import {
   useLanguagePrefs,
   useLanguagePrefsApi,
 } from '#/state/preferences/languages'
+import {Gif} from '#/state/queries/giphy'
 import {useProfileQuery} from '#/state/queries/profile'
 import {ThreadgateSetting} from '#/state/queries/threadgate'
 import {getAgent, useSession} from '#/state/session'
@@ -56,6 +57,9 @@ import {useDialogStateControlContext} from 'state/dialogs'
 import {GalleryModel} from 'state/models/media/gallery'
 import {ComposerOpts} from 'state/shell/composer'
 import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo'
+import {atoms as a} from '#/alf'
+import {Button} from '#/components/Button'
+import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
 import * as Prompt from '#/components/Prompt'
 import {QuoteEmbed} from '../util/post-embeds/QuoteEmbed'
 import {Text} from '../util/text/Text'
@@ -66,6 +70,7 @@ import {ExternalEmbed} from './ExternalEmbed'
 import {LabelsBtn} from './labels/LabelsBtn'
 import {Gallery} from './photos/Gallery'
 import {OpenCameraBtn} from './photos/OpenCameraBtn'
+import {SelectGifBtn} from './photos/SelectGifBtn'
 import {SelectPhotoBtn} from './photos/SelectPhotoBtn'
 import {SelectLangBtn} from './select-language/SelectLangBtn'
 import {SuggestedLanguage} from './select-language/SuggestedLanguage'
@@ -314,13 +319,33 @@ export const ComposePost = observer(function ComposePost({
     ? _(msg`Write your reply`)
     : _(msg`What's up?`)
 
-  const canSelectImages = useMemo(() => gallery.size < 4, [gallery.size])
+  const canSelectImages = gallery.size < 4 && !extLink
   const hasMedia = gallery.size > 0 || Boolean(extLink)
 
   const onEmojiButtonPress = useCallback(() => {
     openPicker?.(textInput.current?.getCursorPosition())
   }, [openPicker])
 
+  const focusTextInput = useCallback(() => {
+    textInput.current?.focus()
+  }, [])
+
+  const onSelectGif = useCallback(
+    (gif: Gif) =>
+      setExtLink({
+        uri: gif.url,
+        isLoading: true,
+        meta: {
+          url: gif.url,
+          image: gif.images.original_still.url,
+          likelyType: LikelyType.HTML,
+          title: `${gif.title} - Find & Share on GIPHY`,
+          description: `ALT: ${gif.alt_text}`,
+        },
+      }),
+    [setExtLink],
+  )
+
   return (
     <KeyboardAvoidingView
       testID="composePostView"
@@ -473,25 +498,27 @@ export const ComposePost = observer(function ComposePost({
         </ScrollView>
         <SuggestedLanguage text={richtext.text} />
         <View style={[pal.border, styles.bottomBar]}>
-          {canSelectImages ? (
-            <>
-              <SelectPhotoBtn gallery={gallery} />
-              <OpenCameraBtn gallery={gallery} />
-            </>
-          ) : null}
-          {!isMobile ? (
-            <Pressable
-              onPress={onEmojiButtonPress}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Open emoji picker`)}
-              accessibilityHint={_(msg`Open emoji picker`)}>
-              <FontAwesomeIcon
-                icon={['far', 'face-smile']}
-                color={pal.colors.link}
-                size={22}
-              />
-            </Pressable>
-          ) : null}
+          <View style={[a.flex_row, a.align_center, a.gap_xs]}>
+            <SelectPhotoBtn gallery={gallery} disabled={!canSelectImages} />
+            <OpenCameraBtn gallery={gallery} disabled={!canSelectImages} />
+            <SelectGifBtn
+              onClose={focusTextInput}
+              onSelectGif={onSelectGif}
+              disabled={hasMedia}
+            />
+            {!isMobile ? (
+              <Button
+                onPress={onEmojiButtonPress}
+                style={a.p_sm}
+                label={_(msg`Open emoji picker`)}
+                accessibilityHint={_(msg`Open emoji picker`)}
+                variant="ghost"
+                shape="round"
+                color="primary">
+                <EmojiSmile size="lg" />
+              </Button>
+            ) : null}
+          </View>
           <View style={s.flex1} />
           <SelectLangBtn />
           <CharProgress count={graphemeLength} />
@@ -586,7 +613,7 @@ const styles = StyleSheet.create({
   },
   bottomBar: {
     flexDirection: 'row',
-    paddingVertical: 10,
+    paddingVertical: 4,
     paddingLeft: 15,
     paddingRight: 20,
     alignItems: 'center',
diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx
index 4353704d5..8f9152e34 100644
--- a/src/view/com/composer/photos/OpenCameraBtn.tsx
+++ b/src/view/com/composer/photos/OpenCameraBtn.tsx
@@ -1,32 +1,31 @@
 import React, {useCallback} from 'react'
-import {TouchableOpacity, StyleSheet} from 'react-native'
 import * as MediaLibrary from 'expo-media-library'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useAnalytics} from 'lib/analytics/analytics'
-import {openCamera} from 'lib/media/picker'
-import {useCameraPermission} from 'lib/hooks/usePermissions'
-import {HITSLOP_10, POST_IMG_MAX} from 'lib/constants'
-import {GalleryModel} from 'state/models/media/gallery'
-import {isMobileWeb, isNative} from 'platform/detection'
-import {logger} from '#/logger'
-import {useLingui} from '@lingui/react'
 import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useAnalytics} from '#/lib/analytics/analytics'
+import {POST_IMG_MAX} from '#/lib/constants'
+import {useCameraPermission} from '#/lib/hooks/usePermissions'
+import {openCamera} from '#/lib/media/picker'
+import {logger} from '#/logger'
+import {isMobileWeb, isNative} from '#/platform/detection'
+import {GalleryModel} from '#/state/models/media/gallery'
+import {atoms as a, useTheme} from '#/alf'
+import {Button} from '#/components/Button'
+import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
 
 type Props = {
   gallery: GalleryModel
+  disabled?: boolean
 }
 
-export function OpenCameraBtn({gallery}: Props) {
-  const pal = usePalette('default')
+export function OpenCameraBtn({gallery, disabled}: Props) {
   const {track} = useAnalytics()
   const {_} = useLingui()
   const {requestCameraAccessIfNeeded} = useCameraPermission()
   const [mediaPermissionRes, requestMediaPermission] =
     MediaLibrary.usePermissions()
+  const t = useTheme()
 
   const onPressTakePicture = useCallback(async () => {
     track('Composer:CameraOpened')
@@ -68,25 +67,17 @@ export function OpenCameraBtn({gallery}: Props) {
   }
 
   return (
-    <TouchableOpacity
+    <Button
       testID="openCameraButton"
       onPress={onPressTakePicture}
-      style={styles.button}
-      hitSlop={HITSLOP_10}
-      accessibilityRole="button"
-      accessibilityLabel={_(msg`Camera`)}
-      accessibilityHint={_(msg`Opens camera on device`)}>
-      <FontAwesomeIcon
-        icon="camera"
-        style={pal.link as FontAwesomeIconStyle}
-        size={24}
-      />
-    </TouchableOpacity>
+      label={_(msg`Camera`)}
+      accessibilityHint={_(msg`Opens camera on device`)}
+      style={a.p_sm}
+      variant="ghost"
+      shape="round"
+      color="primary"
+      disabled={disabled}>
+      <Camera size="lg" style={disabled && t.atoms.text_contrast_low} />
+    </Button>
   )
 }
-
-const styles = StyleSheet.create({
-  button: {
-    paddingHorizontal: 15,
-  },
-})
diff --git a/src/view/com/composer/photos/SelectGifBtn.tsx b/src/view/com/composer/photos/SelectGifBtn.tsx
new file mode 100644
index 000000000..31310fdc1
--- /dev/null
+++ b/src/view/com/composer/photos/SelectGifBtn.tsx
@@ -0,0 +1,53 @@
+import React, {useCallback} from 'react'
+import {Keyboard} from 'react-native'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {logEvent} from '#/lib/statsig/statsig'
+import {Gif} from '#/state/queries/giphy'
+import {atoms as a, useTheme} from '#/alf'
+import {Button} from '#/components/Button'
+import {useDialogControl} from '#/components/Dialog'
+import {GifSelectDialog} from '#/components/dialogs/GifSelect'
+import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
+
+type Props = {
+  onClose: () => void
+  onSelectGif: (gif: Gif) => void
+  disabled?: boolean
+}
+
+export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
+  const {_} = useLingui()
+  const control = useDialogControl()
+  const t = useTheme()
+
+  const onPressSelectGif = useCallback(async () => {
+    logEvent('composer:gif:open', {})
+    Keyboard.dismiss()
+    control.open()
+  }, [control])
+
+  return (
+    <>
+      <Button
+        testID="openGifBtn"
+        onPress={onPressSelectGif}
+        label={_(msg`Select GIF`)}
+        accessibilityHint={_(msg`Opens GIF select dialog`)}
+        style={a.p_sm}
+        variant="ghost"
+        shape="round"
+        color="primary"
+        disabled={disabled}>
+        <GifIcon size="lg" style={disabled && t.atoms.text_contrast_low} />
+      </Button>
+
+      <GifSelectDialog
+        control={control}
+        onClose={onClose}
+        onSelectGif={onSelectGif}
+      />
+    </>
+  )
+}
diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx
index f7fa9502d..747653fc8 100644
--- a/src/view/com/composer/photos/SelectPhotoBtn.tsx
+++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx
@@ -1,27 +1,26 @@
+/* eslint-disable react-native-a11y/has-valid-accessibility-ignores-invert-colors */
 import React, {useCallback} from 'react'
-import {TouchableOpacity, StyleSheet} from 'react-native'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useAnalytics} from 'lib/analytics/analytics'
-import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
-import {GalleryModel} from 'state/models/media/gallery'
-import {HITSLOP_10} from 'lib/constants'
-import {isNative} from 'platform/detection'
-import {useLingui} from '@lingui/react'
 import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useAnalytics} from '#/lib/analytics/analytics'
+import {usePhotoLibraryPermission} from '#/lib/hooks/usePermissions'
+import {isNative} from '#/platform/detection'
+import {GalleryModel} from '#/state/models/media/gallery'
+import {atoms as a, useTheme} from '#/alf'
+import {Button} from '#/components/Button'
+import {Image_Stroke2_Corner0_Rounded as Image} from '#/components/icons/Image'
 
 type Props = {
   gallery: GalleryModel
+  disabled?: boolean
 }
 
-export function SelectPhotoBtn({gallery}: Props) {
-  const pal = usePalette('default')
+export function SelectPhotoBtn({gallery, disabled}: Props) {
   const {track} = useAnalytics()
   const {_} = useLingui()
   const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
+  const t = useTheme()
 
   const onPressSelectPhotos = useCallback(async () => {
     track('Composer:GalleryOpened')
@@ -34,25 +33,17 @@ export function SelectPhotoBtn({gallery}: Props) {
   }, [track, requestPhotoAccessIfNeeded, gallery])
 
   return (
-    <TouchableOpacity
+    <Button
       testID="openGalleryBtn"
       onPress={onPressSelectPhotos}
-      style={styles.button}
-      hitSlop={HITSLOP_10}
-      accessibilityRole="button"
-      accessibilityLabel={_(msg`Gallery`)}
-      accessibilityHint={_(msg`Opens device photo gallery`)}>
-      <FontAwesomeIcon
-        icon={['far', 'image']}
-        style={pal.link as FontAwesomeIconStyle}
-        size={24}
-      />
-    </TouchableOpacity>
+      label={_(msg`Gallery`)}
+      accessibilityHint={_(msg`Opens device photo gallery`)}
+      style={a.p_sm}
+      variant="ghost"
+      shape="round"
+      color="primary"
+      disabled={disabled}>
+      <Image size="lg" style={disabled && t.atoms.text_contrast_low} />
+    </Button>
   )
 }
-
-const styles = StyleSheet.create({
-  button: {
-    paddingHorizontal: 15,
-  },
-})
diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx
index cae8ec314..b532b0dd1 100644
--- a/src/view/screens/Storybook/Buttons.tsx
+++ b/src/view/screens/Storybook/Buttons.tsx
@@ -9,7 +9,7 @@ import {
   ButtonText,
   ButtonVariant,
 } from '#/components/Button'
-import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
+import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/Arrow'
 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
 import {H1} from '#/components/Typography'
diff --git a/src/view/screens/Storybook/Icons.tsx b/src/view/screens/Storybook/Icons.tsx
index 9d7dc0aa8..bff1fdc9b 100644
--- a/src/view/screens/Storybook/Icons.tsx
+++ b/src/view/screens/Storybook/Icons.tsx
@@ -2,11 +2,11 @@ import React from 'react'
 import {View} from 'react-native'
 
 import {atoms as a, useTheme} from '#/alf'
-import {H1} from '#/components/Typography'
-import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
-import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
+import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/Arrow'
 import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
+import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
 import {Loader} from '#/components/Loader'
+import {H1} from '#/components/Typography'
 
 export function Icons() {
   const t = useTheme()