about summary refs log tree commit diff
path: root/src/view/screens/Feeds.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/screens/Feeds.tsx')
-rw-r--r--src/view/screens/Feeds.tsx748
1 files changed, 371 insertions, 377 deletions
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index f8fc0db17..ced8592c5 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -2,7 +2,6 @@ import React from 'react'
 import {ActivityIndicator, StyleSheet, View, RefreshControl} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
-import {withAuthRequired} from 'view/com/auth/withAuthRequired'
 import {ViewHeader} from 'view/com/util/ViewHeader'
 import {FAB} from 'view/com/util/fab/FAB'
 import {Link} from 'view/com/util/Link'
@@ -88,437 +87,432 @@ type FlatlistSlice =
       key: string
     }
 
-export const FeedsScreen = withAuthRequired(
-  function FeedsScreenImpl(_props: Props) {
-    const pal = usePalette('default')
-    const {openComposer} = useComposerControls()
-    const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
-    const [query, setQuery] = React.useState('')
-    const [isPTR, setIsPTR] = React.useState(false)
-    const {
-      data: preferences,
-      isLoading: isPreferencesLoading,
-      error: preferencesError,
-    } = usePreferencesQuery()
-    const {
-      data: popularFeeds,
-      isFetching: isPopularFeedsFetching,
-      error: popularFeedsError,
-      refetch: refetchPopularFeeds,
-      fetchNextPage: fetchNextPopularFeedsPage,
-      isFetchingNextPage: isPopularFeedsFetchingNextPage,
-      hasNextPage: hasNextPopularFeedsPage,
-    } = useGetPopularFeedsQuery()
-    const {_} = useLingui()
-    const setMinimalShellMode = useSetMinimalShellMode()
-    const {
-      data: searchResults,
-      mutate: search,
-      reset: resetSearch,
-      isPending: isSearchPending,
-      error: searchError,
-    } = useSearchPopularFeedsMutation()
-    const {hasSession} = useSession()
+export function FeedsScreen(_props: Props) {
+  const pal = usePalette('default')
+  const {openComposer} = useComposerControls()
+  const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
+  const [query, setQuery] = React.useState('')
+  const [isPTR, setIsPTR] = React.useState(false)
+  const {
+    data: preferences,
+    isLoading: isPreferencesLoading,
+    error: preferencesError,
+  } = usePreferencesQuery()
+  const {
+    data: popularFeeds,
+    isFetching: isPopularFeedsFetching,
+    error: popularFeedsError,
+    refetch: refetchPopularFeeds,
+    fetchNextPage: fetchNextPopularFeedsPage,
+    isFetchingNextPage: isPopularFeedsFetchingNextPage,
+    hasNextPage: hasNextPopularFeedsPage,
+  } = useGetPopularFeedsQuery()
+  const {_} = useLingui()
+  const setMinimalShellMode = useSetMinimalShellMode()
+  const {
+    data: searchResults,
+    mutate: search,
+    reset: resetSearch,
+    isPending: isSearchPending,
+    error: searchError,
+  } = useSearchPopularFeedsMutation()
+  const {hasSession} = useSession()
 
-    /**
-     * A search query is present. We may not have search results yet.
-     */
-    const isUserSearching = query.length > 1
-    const debouncedSearch = React.useMemo(
-      () => debounce(q => search(q), 500), // debounce for 500ms
-      [search],
-    )
-    const onPressCompose = React.useCallback(() => {
-      openComposer({})
-    }, [openComposer])
-    const onChangeQuery = React.useCallback(
-      (text: string) => {
-        setQuery(text)
-        if (text.length > 1) {
-          debouncedSearch(text)
-        } else {
-          refetchPopularFeeds()
-          resetSearch()
-        }
-      },
-      [setQuery, refetchPopularFeeds, debouncedSearch, resetSearch],
+  /**
+   * A search query is present. We may not have search results yet.
+   */
+  const isUserSearching = query.length > 1
+  const debouncedSearch = React.useMemo(
+    () => debounce(q => search(q), 500), // debounce for 500ms
+    [search],
+  )
+  const onPressCompose = React.useCallback(() => {
+    openComposer({})
+  }, [openComposer])
+  const onChangeQuery = React.useCallback(
+    (text: string) => {
+      setQuery(text)
+      if (text.length > 1) {
+        debouncedSearch(text)
+      } else {
+        refetchPopularFeeds()
+        resetSearch()
+      }
+    },
+    [setQuery, refetchPopularFeeds, debouncedSearch, resetSearch],
+  )
+  const onPressCancelSearch = React.useCallback(() => {
+    setQuery('')
+    refetchPopularFeeds()
+    resetSearch()
+  }, [refetchPopularFeeds, setQuery, resetSearch])
+  const onSubmitQuery = React.useCallback(() => {
+    debouncedSearch(query)
+  }, [query, debouncedSearch])
+  const onPullToRefresh = React.useCallback(async () => {
+    setIsPTR(true)
+    await refetchPopularFeeds()
+    setIsPTR(false)
+  }, [setIsPTR, refetchPopularFeeds])
+  const onEndReached = React.useCallback(() => {
+    if (
+      isPopularFeedsFetching ||
+      isUserSearching ||
+      !hasNextPopularFeedsPage ||
+      popularFeedsError
     )
-    const onPressCancelSearch = React.useCallback(() => {
-      setQuery('')
-      refetchPopularFeeds()
-      resetSearch()
-    }, [refetchPopularFeeds, setQuery, resetSearch])
-    const onSubmitQuery = React.useCallback(() => {
-      debouncedSearch(query)
-    }, [query, debouncedSearch])
-    const onPullToRefresh = React.useCallback(async () => {
-      setIsPTR(true)
-      await refetchPopularFeeds()
-      setIsPTR(false)
-    }, [setIsPTR, refetchPopularFeeds])
-    const onEndReached = React.useCallback(() => {
-      if (
-        isPopularFeedsFetching ||
-        isUserSearching ||
-        !hasNextPopularFeedsPage ||
-        popularFeedsError
-      )
-        return
-      fetchNextPopularFeedsPage()
-    }, [
-      isPopularFeedsFetching,
-      isUserSearching,
-      popularFeedsError,
-      hasNextPopularFeedsPage,
-      fetchNextPopularFeedsPage,
-    ])
+      return
+    fetchNextPopularFeedsPage()
+  }, [
+    isPopularFeedsFetching,
+    isUserSearching,
+    popularFeedsError,
+    hasNextPopularFeedsPage,
+    fetchNextPopularFeedsPage,
+  ])
 
-    useFocusEffect(
-      React.useCallback(() => {
-        setMinimalShellMode(false)
-      }, [setMinimalShellMode]),
-    )
+  useFocusEffect(
+    React.useCallback(() => {
+      setMinimalShellMode(false)
+    }, [setMinimalShellMode]),
+  )
 
-    const items = React.useMemo(() => {
-      let slices: FlatlistSlice[] = []
+  const items = React.useMemo(() => {
+    let slices: FlatlistSlice[] = []
 
-      if (hasSession) {
+    if (hasSession) {
+      slices.push({
+        key: 'savedFeedsHeader',
+        type: 'savedFeedsHeader',
+      })
+
+      if (preferencesError) {
         slices.push({
-          key: 'savedFeedsHeader',
-          type: 'savedFeedsHeader',
+          key: 'savedFeedsError',
+          type: 'error',
+          error: cleanError(preferencesError.toString()),
         })
-
-        if (preferencesError) {
+      } else {
+        if (isPreferencesLoading || !preferences?.feeds?.saved) {
           slices.push({
-            key: 'savedFeedsError',
-            type: 'error',
-            error: cleanError(preferencesError.toString()),
+            key: 'savedFeedsLoading',
+            type: 'savedFeedsLoading',
+            // pendingItems: this.rootStore.preferences.savedFeeds.length || 3,
           })
         } else {
-          if (isPreferencesLoading || !preferences?.feeds?.saved) {
+          if (preferences?.feeds?.saved.length === 0) {
             slices.push({
-              key: 'savedFeedsLoading',
-              type: 'savedFeedsLoading',
-              // pendingItems: this.rootStore.preferences.savedFeeds.length || 3,
+              key: 'savedFeedNoResults',
+              type: 'savedFeedNoResults',
             })
           } else {
-            if (preferences?.feeds?.saved.length === 0) {
-              slices.push({
-                key: 'savedFeedNoResults',
-                type: 'savedFeedNoResults',
-              })
-            } else {
-              const {saved, pinned} = preferences.feeds
+            const {saved, pinned} = preferences.feeds
 
-              slices = slices.concat(
-                pinned.map(uri => ({
+            slices = slices.concat(
+              pinned.map(uri => ({
+                key: `savedFeed:${uri}`,
+                type: 'savedFeed',
+                feedUri: uri,
+              })),
+            )
+
+            slices = slices.concat(
+              saved
+                .filter(uri => !pinned.includes(uri))
+                .map(uri => ({
                   key: `savedFeed:${uri}`,
                   type: 'savedFeed',
                   feedUri: uri,
                 })),
-              )
-
-              slices = slices.concat(
-                saved
-                  .filter(uri => !pinned.includes(uri))
-                  .map(uri => ({
-                    key: `savedFeed:${uri}`,
-                    type: 'savedFeed',
-                    feedUri: uri,
-                  })),
-              )
-            }
+            )
           }
         }
       }
+    }
+
+    slices.push({
+      key: 'popularFeedsHeader',
+      type: 'popularFeedsHeader',
+    })
 
+    if (popularFeedsError || searchError) {
       slices.push({
-        key: 'popularFeedsHeader',
-        type: 'popularFeedsHeader',
+        key: 'popularFeedsError',
+        type: 'error',
+        error: cleanError(
+          popularFeedsError?.toString() ?? searchError?.toString() ?? '',
+        ),
       })
-
-      if (popularFeedsError || searchError) {
-        slices.push({
-          key: 'popularFeedsError',
-          type: 'error',
-          error: cleanError(
-            popularFeedsError?.toString() ?? searchError?.toString() ?? '',
-          ),
-        })
-      } else {
-        if (isUserSearching) {
-          if (isSearchPending || !searchResults) {
+    } else {
+      if (isUserSearching) {
+        if (isSearchPending || !searchResults) {
+          slices.push({
+            key: 'popularFeedsLoading',
+            type: 'popularFeedsLoading',
+          })
+        } else {
+          if (!searchResults || searchResults?.length === 0) {
             slices.push({
-              key: 'popularFeedsLoading',
-              type: 'popularFeedsLoading',
+              key: 'popularFeedsNoResults',
+              type: 'popularFeedsNoResults',
             })
           } else {
-            if (!searchResults || searchResults?.length === 0) {
-              slices.push({
-                key: 'popularFeedsNoResults',
-                type: 'popularFeedsNoResults',
-              })
-            } else {
-              slices = slices.concat(
-                searchResults.map(feed => ({
-                  key: `popularFeed:${feed.uri}`,
-                  type: 'popularFeed',
-                  feedUri: feed.uri,
-                })),
-              )
-            }
+            slices = slices.concat(
+              searchResults.map(feed => ({
+                key: `popularFeed:${feed.uri}`,
+                type: 'popularFeed',
+                feedUri: feed.uri,
+              })),
+            )
           }
+        }
+      } else {
+        if (isPopularFeedsFetching && !popularFeeds?.pages) {
+          slices.push({
+            key: 'popularFeedsLoading',
+            type: 'popularFeedsLoading',
+          })
         } else {
-          if (isPopularFeedsFetching && !popularFeeds?.pages) {
+          if (
+            !popularFeeds?.pages ||
+            popularFeeds?.pages[0]?.feeds?.length === 0
+          ) {
             slices.push({
-              key: 'popularFeedsLoading',
-              type: 'popularFeedsLoading',
+              key: 'popularFeedsNoResults',
+              type: 'popularFeedsNoResults',
             })
           } else {
-            if (
-              !popularFeeds?.pages ||
-              popularFeeds?.pages[0]?.feeds?.length === 0
-            ) {
+            for (const page of popularFeeds.pages || []) {
+              slices = slices.concat(
+                page.feeds
+                  .filter(feed => !preferences?.feeds?.saved.includes(feed.uri))
+                  .map(feed => ({
+                    key: `popularFeed:${feed.uri}`,
+                    type: 'popularFeed',
+                    feedUri: feed.uri,
+                  })),
+              )
+            }
+
+            if (isPopularFeedsFetchingNextPage) {
               slices.push({
-                key: 'popularFeedsNoResults',
-                type: 'popularFeedsNoResults',
+                key: 'popularFeedsLoadingMore',
+                type: 'popularFeedsLoadingMore',
               })
-            } else {
-              for (const page of popularFeeds.pages || []) {
-                slices = slices.concat(
-                  page.feeds
-                    .filter(
-                      feed => !preferences?.feeds?.saved.includes(feed.uri),
-                    )
-                    .map(feed => ({
-                      key: `popularFeed:${feed.uri}`,
-                      type: 'popularFeed',
-                      feedUri: feed.uri,
-                    })),
-                )
-              }
-
-              if (isPopularFeedsFetchingNextPage) {
-                slices.push({
-                  key: 'popularFeedsLoadingMore',
-                  type: 'popularFeedsLoadingMore',
-                })
-              }
             }
           }
         }
       }
+    }
 
-      return slices
-    }, [
-      hasSession,
-      preferences,
-      isPreferencesLoading,
-      preferencesError,
-      popularFeeds,
-      isPopularFeedsFetching,
-      popularFeedsError,
-      isPopularFeedsFetchingNextPage,
-      searchResults,
-      isSearchPending,
-      searchError,
-      isUserSearching,
-    ])
+    return slices
+  }, [
+    hasSession,
+    preferences,
+    isPreferencesLoading,
+    preferencesError,
+    popularFeeds,
+    isPopularFeedsFetching,
+    popularFeedsError,
+    isPopularFeedsFetchingNextPage,
+    searchResults,
+    isSearchPending,
+    searchError,
+    isUserSearching,
+  ])
 
-    const renderHeaderBtn = React.useCallback(() => {
-      return (
-        <Link
-          href="/settings/saved-feeds"
-          hitSlop={10}
-          accessibilityRole="button"
-          accessibilityLabel={_(msg`Edit Saved Feeds`)}
-          accessibilityHint="Opens screen to edit Saved Feeds">
-          <CogIcon size={22} strokeWidth={2} style={pal.textLight} />
-        </Link>
-      )
-    }, [pal, _])
+  const renderHeaderBtn = React.useCallback(() => {
+    return (
+      <Link
+        href="/settings/saved-feeds"
+        hitSlop={10}
+        accessibilityRole="button"
+        accessibilityLabel={_(msg`Edit Saved Feeds`)}
+        accessibilityHint="Opens screen to edit Saved Feeds">
+        <CogIcon size={22} strokeWidth={2} style={pal.textLight} />
+      </Link>
+    )
+  }, [pal, _])
 
-    const renderItem = React.useCallback(
-      ({item}: {item: FlatlistSlice}) => {
-        if (item.type === 'error') {
-          return <ErrorMessage message={item.error} />
-        } else if (
-          item.type === 'popularFeedsLoadingMore' ||
-          item.type === 'savedFeedsLoading'
-        ) {
-          return (
-            <View style={s.p10}>
-              <ActivityIndicator />
-            </View>
-          )
-        } else if (item.type === 'savedFeedsHeader') {
-          if (!isMobile) {
-            return (
-              <View
-                style={[
-                  pal.view,
-                  styles.header,
-                  pal.border,
-                  {
-                    borderBottomWidth: 1,
-                  },
-                ]}>
-                <Text type="title-lg" style={[pal.text, s.bold]}>
-                  <Trans>My Feeds</Trans>
-                </Text>
-                <Link
-                  href="/settings/saved-feeds"
-                  accessibilityLabel={_(msg`Edit My Feeds`)}
-                  accessibilityHint="">
-                  <CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
-                </Link>
-              </View>
-            )
-          }
-          return <View />
-        } else if (item.type === 'savedFeedNoResults') {
+  const renderItem = React.useCallback(
+    ({item}: {item: FlatlistSlice}) => {
+      if (item.type === 'error') {
+        return <ErrorMessage message={item.error} />
+      } else if (
+        item.type === 'popularFeedsLoadingMore' ||
+        item.type === 'savedFeedsLoading'
+      ) {
+        return (
+          <View style={s.p10}>
+            <ActivityIndicator />
+          </View>
+        )
+      } else if (item.type === 'savedFeedsHeader') {
+        if (!isMobile) {
           return (
             <View
-              style={{
-                paddingHorizontal: 16,
-                paddingTop: 10,
-              }}>
-              <Text type="lg" style={pal.textLight}>
-                <Trans>You don't have any saved feeds!</Trans>
+              style={[
+                pal.view,
+                styles.header,
+                pal.border,
+                {
+                  borderBottomWidth: 1,
+                },
+              ]}>
+              <Text type="title-lg" style={[pal.text, s.bold]}>
+                <Trans>My Feeds</Trans>
               </Text>
+              <Link
+                href="/settings/saved-feeds"
+                accessibilityLabel={_(msg`Edit My Feeds`)}
+                accessibilityHint="">
+                <CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
+              </Link>
             </View>
           )
-        } else if (item.type === 'savedFeed') {
-          return <SavedFeed feedUri={item.feedUri} />
-        } else if (item.type === 'popularFeedsHeader') {
-          return (
-            <>
-              <View
-                style={[
-                  pal.view,
-                  styles.header,
-                  {
-                    // This is first in the flatlist without a session -esb
-                    marginTop: hasSession ? 16 : 0,
-                    paddingLeft: isMobile ? 12 : undefined,
-                    paddingRight: 10,
-                    paddingBottom: isMobile ? 6 : undefined,
-                  },
-                ]}>
-                <Text type="title-lg" style={[pal.text, s.bold]}>
-                  <Trans>Discover new feeds</Trans>
-                </Text>
-
-                {!isMobile && (
-                  <SearchInput
-                    query={query}
-                    onChangeQuery={onChangeQuery}
-                    onPressCancelSearch={onPressCancelSearch}
-                    onSubmitQuery={onSubmitQuery}
-                    style={{flex: 1, maxWidth: 250}}
-                  />
-                )}
-              </View>
-
-              {isMobile && (
-                <View style={{paddingHorizontal: 8, paddingBottom: 10}}>
-                  <SearchInput
-                    query={query}
-                    onChangeQuery={onChangeQuery}
-                    onPressCancelSearch={onPressCancelSearch}
-                    onSubmitQuery={onSubmitQuery}
-                  />
-                </View>
-              )}
-            </>
-          )
-        } else if (item.type === 'popularFeedsLoading') {
-          return <FeedFeedLoadingPlaceholder />
-        } else if (item.type === 'popularFeed') {
-          return (
-            <FeedSourceCard
-              feedUri={item.feedUri}
-              showSaveBtn={hasSession}
-              showDescription
-              showLikes
-            />
-          )
-        } else if (item.type === 'popularFeedsNoResults') {
-          return (
+        }
+        return <View />
+      } else if (item.type === 'savedFeedNoResults') {
+        return (
+          <View
+            style={{
+              paddingHorizontal: 16,
+              paddingTop: 10,
+            }}>
+            <Text type="lg" style={pal.textLight}>
+              <Trans>You don't have any saved feeds!</Trans>
+            </Text>
+          </View>
+        )
+      } else if (item.type === 'savedFeed') {
+        return <SavedFeed feedUri={item.feedUri} />
+      } else if (item.type === 'popularFeedsHeader') {
+        return (
+          <>
             <View
-              style={{
-                paddingHorizontal: 16,
-                paddingTop: 10,
-                paddingBottom: '150%',
-              }}>
-              <Text type="lg" style={pal.textLight}>
-                <Trans>No results found for "{query}"</Trans>
+              style={[
+                pal.view,
+                styles.header,
+                {
+                  // This is first in the flatlist without a session -esb
+                  marginTop: hasSession ? 16 : 0,
+                  paddingLeft: isMobile ? 12 : undefined,
+                  paddingRight: 10,
+                  paddingBottom: isMobile ? 6 : undefined,
+                },
+              ]}>
+              <Text type="title-lg" style={[pal.text, s.bold]}>
+                <Trans>Discover new feeds</Trans>
               </Text>
+
+              {!isMobile && (
+                <SearchInput
+                  query={query}
+                  onChangeQuery={onChangeQuery}
+                  onPressCancelSearch={onPressCancelSearch}
+                  onSubmitQuery={onSubmitQuery}
+                  style={{flex: 1, maxWidth: 250}}
+                />
+              )}
             </View>
-          )
-        }
-        return null
-      },
-      [
-        _,
-        hasSession,
-        isMobile,
-        pal,
-        query,
-        onChangeQuery,
-        onPressCancelSearch,
-        onSubmitQuery,
-      ],
-    )
 
-    return (
-      <View style={[pal.view, styles.container]}>
-        {isMobile && (
-          <ViewHeader
-            title={_(msg`Feeds`)}
-            canGoBack={false}
-            renderButton={renderHeaderBtn}
-            showBorder
+            {isMobile && (
+              <View style={{paddingHorizontal: 8, paddingBottom: 10}}>
+                <SearchInput
+                  query={query}
+                  onChangeQuery={onChangeQuery}
+                  onPressCancelSearch={onPressCancelSearch}
+                  onSubmitQuery={onSubmitQuery}
+                />
+              </View>
+            )}
+          </>
+        )
+      } else if (item.type === 'popularFeedsLoading') {
+        return <FeedFeedLoadingPlaceholder />
+      } else if (item.type === 'popularFeed') {
+        return (
+          <FeedSourceCard
+            feedUri={item.feedUri}
+            showSaveBtn={hasSession}
+            showDescription
+            showLikes
           />
-        )}
-
-        {preferences ? <View /> : <ActivityIndicator />}
+        )
+      } else if (item.type === 'popularFeedsNoResults') {
+        return (
+          <View
+            style={{
+              paddingHorizontal: 16,
+              paddingTop: 10,
+              paddingBottom: '150%',
+            }}>
+            <Text type="lg" style={pal.textLight}>
+              <Trans>No results found for "{query}"</Trans>
+            </Text>
+          </View>
+        )
+      }
+      return null
+    },
+    [
+      _,
+      hasSession,
+      isMobile,
+      pal,
+      query,
+      onChangeQuery,
+      onPressCancelSearch,
+      onSubmitQuery,
+    ],
+  )
 
-        <FlatList
-          style={[!isTabletOrDesktop && s.flex1, styles.list]}
-          data={items}
-          keyExtractor={item => item.key}
-          contentContainerStyle={styles.contentContainer}
-          renderItem={renderItem}
-          refreshControl={
-            <RefreshControl
-              refreshing={isPTR}
-              onRefresh={isUserSearching ? undefined : onPullToRefresh}
-              tintColor={pal.colors.text}
-              titleColor={pal.colors.text}
-            />
-          }
-          initialNumToRender={10}
-          onEndReached={onEndReached}
-          // @ts-ignore our .web version only -prf
-          desktopFixedHeight
+  return (
+    <View style={[pal.view, styles.container]}>
+      {isMobile && (
+        <ViewHeader
+          title={_(msg`Feeds`)}
+          canGoBack={false}
+          renderButton={renderHeaderBtn}
+          showBorder
         />
+      )}
 
-        {hasSession && (
-          <FAB
-            testID="composeFAB"
-            onPress={onPressCompose}
-            icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
-            accessibilityRole="button"
-            accessibilityLabel={_(msg`New post`)}
-            accessibilityHint=""
+      {preferences ? <View /> : <ActivityIndicator />}
+
+      <FlatList
+        style={[!isTabletOrDesktop && s.flex1, styles.list]}
+        data={items}
+        keyExtractor={item => item.key}
+        contentContainerStyle={styles.contentContainer}
+        renderItem={renderItem}
+        refreshControl={
+          <RefreshControl
+            refreshing={isPTR}
+            onRefresh={isUserSearching ? undefined : onPullToRefresh}
+            tintColor={pal.colors.text}
+            titleColor={pal.colors.text}
           />
-        )}
-      </View>
-    )
-  },
-  {isPublic: true},
-)
+        }
+        initialNumToRender={10}
+        onEndReached={onEndReached}
+        // @ts-ignore our .web version only -prf
+        desktopFixedHeight
+      />
+
+      {hasSession && (
+        <FAB
+          testID="composeFAB"
+          onPress={onPressCompose}
+          icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
+          accessibilityRole="button"
+          accessibilityLabel={_(msg`New post`)}
+          accessibilityHint=""
+        />
+      )}
+    </View>
+  )
+}
 
 function SavedFeed({feedUri}: {feedUri: string}) {
   const pal = usePalette('default')