diff --git a/src/components/dialogs/GifSelect.ios.tsx b/src/components/dialogs/GifSelect.ios.tsx
new file mode 100644
index 00000000..091a23e5
--- /dev/null
+++ b/src/components/dialogs/GifSelect.ios.tsx
@@ -0,0 +1,255 @@
+import React, {
+ useCallback,
+ useImperativeHandle,
+ useMemo,
+ useRef,
+ useState,
+} from 'react'
+import {Modal, ScrollView, TextInput, View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {cleanError} from '#/lib/strings/errors'
+import {
+ Gif,
+ useFeaturedGifsQuery,
+ useGifSearchQuery,
+} from '#/state/queries/tenor'
+import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
+import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
+import {FlatList_INTERNAL} from '#/view/com/util/Views'
+import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import * as TextField from '#/components/forms/TextField'
+import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
+import {Button, ButtonText} from '../Button'
+import {Handle} from '../Dialog'
+import {useThrottledValue} from '../hooks/useThrottledValue'
+import {ListFooter, ListMaybePlaceholder} from '../Lists'
+import {GifPreview} from './GifSelect.shared'
+
+export function GifSelectDialog({
+ controlRef,
+ onClose,
+ onSelectGif: onSelectGifProp,
+}: {
+ controlRef: React.RefObject<{open: () => void}>
+ onClose: () => void
+ onSelectGif: (gif: Gif) => void
+}) {
+ const t = useTheme()
+ const [open, setOpen] = useState(false)
+
+ useImperativeHandle(controlRef, () => ({
+ open: () => setOpen(true),
+ }))
+
+ const close = useCallback(() => {
+ setOpen(false)
+ onClose()
+ }, [onClose])
+
+ const onSelectGif = useCallback(
+ (gif: Gif) => {
+ onSelectGifProp(gif)
+ close()
+ },
+ [onSelectGifProp, close],
+ )
+
+ const renderErrorBoundary = useCallback(
+ (error: any) => ,
+ [close],
+ )
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+function GifList({
+ onSelectGif,
+}: {
+ close: () => void
+ onSelectGif: (gif: Gif) => void
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+ const {gtMobile} = useBreakpoints()
+ const textInputRef = useRef(null)
+ const listRef = useRef(null)
+ const [undeferredSearch, setSearch] = useState('')
+ const search = useThrottledValue(undeferredSearch, 500)
+
+ const isSearching = search.length > 0
+
+ const trendingQuery = useFeaturedGifsQuery()
+ const searchQuery = useGifSearchQuery(search)
+
+ const {
+ data,
+ fetchNextPage,
+ isFetchingNextPage,
+ hasNextPage,
+ error,
+ isLoading,
+ isError,
+ refetch,
+ } = isSearching ? searchQuery : trendingQuery
+
+ const flattenedData = useMemo(() => {
+ return data?.pages.flatMap(page => page.results) || []
+ }, [data])
+
+ const renderItem = useCallback(
+ ({item}: {item: Gif}) => {
+ return
+ },
+ [onSelectGif],
+ )
+
+ const onEndReached = React.useCallback(() => {
+ if (isFetchingNextPage || !hasNextPage || error) return
+ fetchNextPage()
+ }, [isFetchingNextPage, hasNextPage, error, fetchNextPage])
+
+ const hasData = flattenedData.length > 0
+
+ const onGoBack = useCallback(() => {
+ if (isSearching) {
+ // clear the input and reset the state
+ textInputRef.current?.clear()
+ setSearch('')
+ } else {
+ close()
+ }
+ }, [isSearching])
+
+ const listHeader = useMemo(() => {
+ return (
+
+ {/* cover top corners */}
+
+
+
+
+ {
+ setSearch(text)
+ listRef.current?.scrollToOffset({offset: 0, animated: false})
+ }}
+ returnKeyType="search"
+ clearButtonMode="while-editing"
+ inputRef={textInputRef}
+ maxLength={50}
+ />
+
+
+ )
+ }, [t.atoms.bg, _])
+
+ return (
+
+ {listHeader}
+ {!hasData && (
+
+ )}
+ >
+ }
+ stickyHeaderIndices={[0]}
+ onEndReached={onEndReached}
+ onEndReachedThreshold={4}
+ keyExtractor={(item: Gif) => item.id}
+ keyboardDismissMode="on-drag"
+ ListFooterComponent={
+ hasData ? (
+
+ ) : null
+ }
+ />
+ )
+}
+
+function ModalError({details, close}: {details?: string; close: () => void}) {
+ const {_} = useLingui()
+
+ return (
+
+
+
+
+ )
+}
diff --git a/src/components/dialogs/GifSelect.shared.tsx b/src/components/dialogs/GifSelect.shared.tsx
new file mode 100644
index 00000000..90b2abaa
--- /dev/null
+++ b/src/components/dialogs/GifSelect.shared.tsx
@@ -0,0 +1,53 @@
+import React, {useCallback} from 'react'
+import {Image} from 'expo-image'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {logEvent} from '#/lib/statsig/statsig'
+import {Gif} from '#/state/queries/tenor'
+import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {Button} from '../Button'
+
+export function GifPreview({
+ gif,
+ onSelectGif,
+}: {
+ gif: Gif
+ onSelectGif: (gif: Gif) => void
+}) {
+ const {gtTablet} = useBreakpoints()
+ const {_} = useLingui()
+ const t = useTheme()
+
+ const onPress = useCallback(() => {
+ logEvent('composer:gif:select', {})
+ onSelectGif(gif)
+ }, [onSelectGif, gif])
+
+ return (
+
+ )
+}
diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx
index 4a3ce42a..a64edcd6 100644
--- a/src/components/dialogs/GifSelect.tsx
+++ b/src/components/dialogs/GifSelect.tsx
@@ -1,11 +1,15 @@
-import React, {useCallback, useMemo, useRef, useState} from 'react'
+import React, {
+ useCallback,
+ useImperativeHandle,
+ useMemo,
+ useRef,
+ useState,
+} from 'react'
import {TextInput, View} from 'react-native'
-import {Image} from 'expo-image'
import {BottomSheetFlatListMethods} from '@discord/bottom-sheet'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-import {logEvent} from '#/lib/statsig/statsig'
import {cleanError} from '#/lib/strings/errors'
import {isWeb} from '#/platform/detection'
import {
@@ -23,16 +27,23 @@ import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arr
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
import {Button, ButtonIcon, ButtonText} from '../Button'
import {ListFooter, ListMaybePlaceholder} from '../Lists'
+import {GifPreview} from './GifSelect.shared'
export function GifSelectDialog({
- control,
+ controlRef,
onClose,
onSelectGif: onSelectGifProp,
}: {
- control: Dialog.DialogControlProps
+ controlRef: React.RefObject<{open: () => void}>
onClose: () => void
onSelectGif: (gif: Gif) => void
}) {
+ const control = Dialog.useDialogControl()
+
+ useImperativeHandle(controlRef, () => ({
+ open: () => control.open(),
+ }))
+
const onSelectGif = useCallback(
(gif: Gif) => {
control.close(() => onSelectGifProp(gif))
@@ -233,50 +244,6 @@ function GifList({
)
}
-function GifPreview({
- gif,
- onSelectGif,
-}: {
- gif: Gif
- onSelectGif: (gif: Gif) => void
-}) {
- const {gtTablet} = useBreakpoints()
- const {_} = useLingui()
- const t = useTheme()
-
- const onPress = useCallback(() => {
- logEvent('composer:gif:select', {})
- onSelectGif(gif)
- }, [onSelectGif, gif])
-
- return (
-
- )
-}
-
function DialogError({details}: {details?: string}) {
const {_} = useLingui()
const control = Dialog.useDialogContext()
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index ad79cdb5..93cc87fc 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -173,7 +173,7 @@ export const ComposePost = observer(function ComposePost({
)
const onPressCancel = useCallback(() => {
- if (graphemeLength > 0 || !gallery.isEmpty) {
+ if (graphemeLength > 0 || !gallery.isEmpty || extGif) {
closeAllDialogs()
if (Keyboard) {
Keyboard.dismiss()
@@ -183,6 +183,7 @@ export const ComposePost = observer(function ComposePost({
onClose()
}
}, [
+ extGif,
graphemeLength,
gallery.isEmpty,
closeAllDialogs,
@@ -728,8 +729,6 @@ function useAnimatedBorders() {
const styles = StyleSheet.create({
topbar: {},
topbarDesktop: {
- paddingTop: 10,
- paddingBottom: 10,
height: 50,
},
topbarInner: {
diff --git a/src/view/com/composer/photos/SelectGifBtn.tsx b/src/view/com/composer/photos/SelectGifBtn.tsx
index 60cef9a1..d13df0a1 100644
--- a/src/view/com/composer/photos/SelectGifBtn.tsx
+++ b/src/view/com/composer/photos/SelectGifBtn.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback} from 'react'
+import React, {useCallback, useRef} from 'react'
import {Keyboard} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -7,7 +7,6 @@ import {logEvent} from '#/lib/statsig/statsig'
import {Gif} from '#/state/queries/tenor'
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'
@@ -19,14 +18,14 @@ type Props = {
export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
const {_} = useLingui()
- const control = useDialogControl()
+ const ref = useRef<{open: () => void}>(null)
const t = useTheme()
const onPressSelectGif = useCallback(async () => {
logEvent('composer:gif:open', {})
Keyboard.dismiss()
- control.open()
- }, [control])
+ ref.current?.open()
+ }, [])
return (
<>
@@ -44,7 +43,7 @@ export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {