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.tsx164
1 files changed, 135 insertions, 29 deletions
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index 78935edae..826f997dd 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -6,6 +6,7 @@ import {
   StyleSheet,
   View,
 } from 'react-native'
+import {AppBskyActorDefs} from '@atproto/api'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
 import {msg, Trans} from '@lingui/macro'
@@ -44,8 +45,11 @@ import {
 import {Text} from 'view/com/util/text/Text'
 import {UserAvatar} from 'view/com/util/UserAvatar'
 import {ViewHeader} from 'view/com/util/ViewHeader'
+import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
+import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
 import {atoms as a, useTheme} from '#/alf'
 import {IconCircle} from '#/components/IconCircle'
+import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
 import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
 import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
 
@@ -74,6 +78,7 @@ type FlatlistSlice =
       type: 'savedFeed'
       key: string
       feedUri: string
+      savedFeedConfig: AppBskyActorDefs.SavedFeed
     }
   | {
       type: 'savedFeedsLoadMore'
@@ -100,6 +105,10 @@ type FlatlistSlice =
       type: 'popularFeedsLoadingMore'
       key: string
     }
+  | {
+      type: 'noFollowingFeed'
+      key: string
+    }
 
 // HACK
 // the protocol doesn't yet tell us which feeds are personalized
@@ -229,33 +238,54 @@ export function FeedsScreen(_props: Props) {
           error: cleanError(preferencesError.toString()),
         })
       } else {
-        if (isPreferencesLoading || !preferences?.feeds?.saved) {
+        if (isPreferencesLoading || !preferences?.savedFeeds) {
           slices.push({
             key: 'savedFeedsLoading',
             type: 'savedFeedsLoading',
             // pendingItems: this.rootStore.preferences.savedFeeds.length || 3,
           })
         } else {
-          if (preferences?.feeds?.saved.length !== 0) {
-            const {saved, pinned} = preferences.feeds
+          if (preferences.savedFeeds?.length) {
+            const noFollowingFeed = preferences.savedFeeds.every(
+              f => f.type !== 'timeline',
+            )
 
             slices = slices.concat(
-              pinned.map(uri => ({
-                key: `savedFeed:${uri}`,
-                type: 'savedFeed',
-                feedUri: uri,
-              })),
+              preferences.savedFeeds
+                .filter(f => {
+                  return f.pinned
+                })
+                .map(feed => ({
+                  key: `savedFeed:${feed.value}:${feed.id}`,
+                  type: 'savedFeed',
+                  feedUri: feed.value,
+                  savedFeedConfig: feed,
+                })),
             )
-
             slices = slices.concat(
-              saved
-                .filter(uri => !pinned.includes(uri))
-                .map(uri => ({
-                  key: `savedFeed:${uri}`,
+              preferences.savedFeeds
+                .filter(f => {
+                  return !f.pinned
+                })
+                .map(feed => ({
+                  key: `savedFeed:${feed.value}:${feed.id}`,
                   type: 'savedFeed',
-                  feedUri: uri,
+                  feedUri: feed.value,
+                  savedFeedConfig: feed,
                 })),
             )
+
+            if (noFollowingFeed) {
+              slices.push({
+                key: 'noFollowingFeed',
+                type: 'noFollowingFeed',
+              })
+            }
+          } else {
+            slices.push({
+              key: 'savedFeedNoResults',
+              type: 'savedFeedNoResults',
+            })
           }
         }
       }
@@ -323,7 +353,12 @@ export function FeedsScreen(_props: Props) {
                     ) {
                       return false
                     }
-                    return !preferences?.feeds?.saved.includes(feed.uri)
+                    const alreadySaved = Boolean(
+                      preferences?.savedFeeds?.find(f => {
+                        return f.value === feed.uri
+                      }),
+                    )
+                    return !alreadySaved
                   })
                   .map(feed => ({
                     key: `popularFeed:${feed.uri}`,
@@ -463,23 +498,23 @@ export function FeedsScreen(_props: Props) {
                 </View>
               </View>
             )}
-            {preferences?.feeds?.saved?.length !== 0 && <FeedsSavedHeader />}
+            <FeedsSavedHeader />
           </>
         )
       } 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>
+            style={[
+              pal.border,
+              {
+                borderBottomWidth: 1,
+              },
+            ]}>
+            <NoSavedFeedsOfAnyType />
           </View>
         )
       } else if (item.type === 'savedFeed') {
-        return <SavedFeed feedUri={item.feedUri} />
+        return <FeedOrFollowing savedFeedConfig={item.savedFeedConfig} />
       } else if (item.type === 'popularFeedsHeader') {
         return (
           <>
@@ -521,6 +556,18 @@ export function FeedsScreen(_props: Props) {
             </Text>
           </View>
         )
+      } else if (item.type === 'noFollowingFeed') {
+        return (
+          <View
+            style={[
+              pal.border,
+              {
+                borderBottomWidth: 1,
+              },
+            ]}>
+            <NoFollowingFeed />
+          </View>
+        )
       }
       return null
     },
@@ -532,7 +579,6 @@ export function FeedsScreen(_props: Props) {
       pal.icon,
       pal.textLight,
       _,
-      preferences?.feeds?.saved?.length,
       query,
       onChangeQuery,
       onPressCancelSearch,
@@ -585,16 +631,75 @@ export function FeedsScreen(_props: Props) {
   )
 }
 
-function SavedFeed({feedUri}: {feedUri: string}) {
+function FeedOrFollowing({
+  savedFeedConfig: feed,
+}: {
+  savedFeedConfig: AppBskyActorDefs.SavedFeed
+}) {
+  return feed.type === 'timeline' ? (
+    <FollowingFeed />
+  ) : (
+    <SavedFeed savedFeedConfig={feed} />
+  )
+}
+
+function FollowingFeed() {
   const pal = usePalette('default')
+  const t = useTheme()
   const {isMobile} = useWebMediaQueries()
-  const {data: info, error} = useFeedSourceInfoQuery({uri: feedUri})
-  const typeAvatar = getAvatarTypeFromUri(feedUri)
+  return (
+    <View
+      testID={`saved-feed-timeline`}
+      style={[
+        pal.border,
+        styles.savedFeed,
+        isMobile && styles.savedFeedMobile,
+      ]}>
+      <View
+        style={[
+          a.align_center,
+          a.justify_center,
+          {
+            width: 28,
+            height: 28,
+            borderRadius: 3,
+            backgroundColor: t.palette.primary_500,
+          },
+        ]}>
+        <FilterTimeline
+          style={[
+            {
+              width: 18,
+              height: 18,
+            },
+          ]}
+          fill={t.palette.white}
+        />
+      </View>
+      <View
+        style={{flex: 1, flexDirection: 'row', gap: 8, alignItems: 'center'}}>
+        <Text type="lg-medium" style={pal.text} numberOfLines={1}>
+          <Trans>Following</Trans>
+        </Text>
+      </View>
+    </View>
+  )
+}
+
+function SavedFeed({
+  savedFeedConfig: feed,
+}: {
+  savedFeedConfig: AppBskyActorDefs.SavedFeed
+}) {
+  const pal = usePalette('default')
+  const {isMobile} = useWebMediaQueries()
+  const {data: info, error} = useFeedSourceInfoQuery({uri: feed.value})
+  const typeAvatar = getAvatarTypeFromUri(feed.value)
 
   if (!info)
     return (
       <SavedFeedLoadingPlaceholder
-        key={`savedFeedLoadingPlaceholder:${feedUri}`}
+        key={`savedFeedLoadingPlaceholder:${feed.value}`}
       />
     )
 
@@ -632,6 +737,7 @@ function SavedFeed({feedUri}: {feedUri: string}) {
           </View>
         ) : null}
       </View>
+
       {isMobile && (
         <FontAwesomeIcon
           icon="chevron-right"