about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-05-17 20:33:58 -0500
committerPaul Frazee <pfrazee@gmail.com>2023-05-17 20:33:58 -0500
commitf0003d193182bd70935ca6b7e67897922297deed (patch)
tree84deae13e9ed24cb7583b9d463ce3de4ce10adf8 /src
parentc55ce6de020d6c9df86c71158251caddf12da777 (diff)
downloadvoidsky-f0003d193182bd70935ca6b7e67897922297deed.tar.zst
Add 'my feeds' tab
Diffstat (limited to 'src')
-rw-r--r--src/state/models/ui/saved-feeds.ts44
-rw-r--r--src/view/com/feeds/CustomFeed.tsx4
-rw-r--r--src/view/com/feeds/SavedFeeds.tsx163
-rw-r--r--src/view/com/pager/FeedsTabBarMobile.tsx1
-rw-r--r--src/view/screens/Home.tsx10
5 files changed, 198 insertions, 24 deletions
diff --git a/src/state/models/ui/saved-feeds.ts b/src/state/models/ui/saved-feeds.ts
index dca079b72..bae98fc84 100644
--- a/src/state/models/ui/saved-feeds.ts
+++ b/src/state/models/ui/saved-feeds.ts
@@ -139,8 +139,8 @@ export class SavedFeedsModel {
   // public api
   // =
 
-  async refresh() {
-    return this.loadMore(true)
+  async refresh(quietRefresh = false) {
+    return this.loadMore(true, quietRefresh)
   }
 
   clear() {
@@ -153,26 +153,28 @@ export class SavedFeedsModel {
     this.feeds = []
   }
 
-  loadMore = bundleAsync(async (replace: boolean = false) => {
-    if (!replace && !this.hasMore) {
-      return
-    }
-    this._xLoading(replace)
-    try {
-      const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
-        limit: PAGE_SIZE,
-        cursor: replace ? undefined : this.loadMoreCursor,
-      })
-      if (replace) {
-        this._replaceAll(res)
-      } else {
-        this._appendAll(res)
+  loadMore = bundleAsync(
+    async (replace: boolean = false, quietRefresh = false) => {
+      if (!replace && !this.hasMore) {
+        return
       }
-      this._xIdle()
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-  })
+      this._xLoading(replace && !quietRefresh)
+      try {
+        const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
+          limit: PAGE_SIZE,
+          cursor: replace ? undefined : this.loadMoreCursor,
+        })
+        if (replace) {
+          this._replaceAll(res)
+        } else {
+          this._appendAll(res)
+        }
+        this._xIdle()
+      } catch (e: any) {
+        this._xIdle(e)
+      }
+    },
+  )
 
   removeFeed(uri: string) {
     this.feeds = this.feeds.filter(f => f.data.uri !== uri)
diff --git a/src/view/com/feeds/CustomFeed.tsx b/src/view/com/feeds/CustomFeed.tsx
index 5a93020a0..5440a8e8f 100644
--- a/src/view/com/feeds/CustomFeed.tsx
+++ b/src/view/com/feeds/CustomFeed.tsx
@@ -24,11 +24,13 @@ export const CustomFeed = observer(
     item,
     style,
     showSaveBtn = false,
+    showDescription = false,
     showLikes = false,
   }: {
     item: CustomFeedModel
     style?: StyleProp<ViewStyle>
     showSaveBtn?: boolean
+    showDescription?: boolean
     showLikes?: boolean
   }) => {
     const store = useStores()
@@ -75,7 +77,7 @@ export const CustomFeed = observer(
           )}
         </View>
 
-        {item.data.description ? (
+        {showDescription && item.data.description ? (
           <Text style={[pal.textLight, styles.description]} numberOfLines={3}>
             {item.data.description}
           </Text>
diff --git a/src/view/com/feeds/SavedFeeds.tsx b/src/view/com/feeds/SavedFeeds.tsx
new file mode 100644
index 000000000..66a4efecf
--- /dev/null
+++ b/src/view/com/feeds/SavedFeeds.tsx
@@ -0,0 +1,163 @@
+import React, {useEffect, useCallback} from 'react'
+import {
+  ActivityIndicator,
+  FlatList,
+  RefreshControl,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from 'react-native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {usePalette} from 'lib/hooks/usePalette'
+import {observer} from 'mobx-react-lite'
+import {useStores} from 'state/index'
+import {CustomFeedModel} from 'state/models/feeds/custom-feed'
+import {SavedFeedsModel} from 'state/models/ui/saved-feeds'
+import {CenteredView} from 'view/com/util/Views'
+import {Text} from 'view/com/util/text/Text'
+import {isDesktopWeb} from 'platform/detection'
+import {s, colors} from 'lib/styles'
+import {Link} from 'view/com/util/Link'
+import {CustomFeed} from 'view/com/feeds/CustomFeed'
+
+export const SavedFeeds = observer(
+  ({
+    headerOffset = 0,
+    isPageFocused,
+  }: {
+    headerOffset?: number
+    isPageFocused: boolean
+  }) => {
+    const pal = usePalette('default')
+    const store = useStores()
+
+    useEffect(() => {
+      if (isPageFocused) {
+        store.shell.setMinimalShellMode(false)
+        store.me.savedFeeds.refresh(true)
+      }
+    }, [store, isPageFocused])
+
+    const renderListEmptyComponent = useCallback(() => {
+      return (
+        <View
+          style={[
+            pal.border,
+            !isDesktopWeb && s.flex1,
+            pal.viewLight,
+            styles.empty,
+          ]}>
+          <Text type="lg" style={[pal.text]}>
+            You don't have any saved feeds. You can find feeds by searching on
+            Bluesky.
+          </Text>
+        </View>
+      )
+    }, [pal])
+
+    const renderListFooterComponent = useCallback(() => {
+      return (
+        <Link
+          style={[styles.footerLink, pal.border]}
+          href="/settings/pinned-feeds">
+          <FontAwesomeIcon icon="cog" size={18} color={pal.colors.icon} />
+          <Text type="lg-medium" style={pal.textLight}>
+            Settings
+          </Text>
+        </Link>
+      )
+    }, [pal])
+
+    const renderItem = useCallback(
+      ({item}) => (
+        <SavedFeedItem
+          key={item.data.uri}
+          item={item}
+          savedFeeds={store.me.savedFeeds}
+        />
+      ),
+      [store.me.savedFeeds],
+    )
+
+    return (
+      <CenteredView style={[s.flex1]}>
+        <FlatList
+          style={[!isDesktopWeb && s.flex1, {paddingTop: headerOffset}]}
+          data={store.me.savedFeeds.feeds}
+          keyExtractor={item => item.data.uri}
+          refreshing={store.me.savedFeeds.isRefreshing}
+          refreshControl={
+            <RefreshControl
+              refreshing={store.me.savedFeeds.isRefreshing}
+              onRefresh={() => store.me.savedFeeds.refresh()}
+              tintColor={pal.colors.text}
+              titleColor={pal.colors.text}
+              progressViewOffset={headerOffset}
+            />
+          }
+          renderItem={renderItem}
+          initialNumToRender={10}
+          ListFooterComponent={renderListFooterComponent}
+          ListEmptyComponent={renderListEmptyComponent}
+          extraData={store.me.savedFeeds.isLoading}
+          contentOffset={{x: 0, y: headerOffset * -1}}
+          // @ts-ignore our .web version only -prf
+          desktopFixedHeight
+        />
+      </CenteredView>
+    )
+  },
+)
+
+const SavedFeedItem = observer(
+  ({
+    item,
+    savedFeeds,
+  }: {
+    item: CustomFeedModel
+    savedFeeds: SavedFeedsModel
+  }) => {
+    const isPinned = savedFeeds.isPinned(item)
+    const onTogglePinned = useCallback(
+      () => savedFeeds.togglePinnedFeed(item),
+      [savedFeeds, item],
+    )
+
+    return (
+      <View style={styles.itemContainer}>
+        <CustomFeed key={item.data.uri} item={item} />
+        <TouchableOpacity accessibilityRole="button" onPress={onTogglePinned}>
+          <FontAwesomeIcon
+            icon="thumb-tack"
+            size={20}
+            color={isPinned ? colors.blue3 : colors.gray3}
+          />
+        </TouchableOpacity>
+      </View>
+    )
+  },
+)
+
+const styles = StyleSheet.create({
+  footerLink: {
+    flexDirection: 'row',
+    borderTopWidth: 1,
+    borderBottomWidth: 1,
+    paddingHorizontal: 26,
+    paddingVertical: 18,
+    gap: 18,
+  },
+  empty: {
+    paddingHorizontal: 18,
+    paddingVertical: 16,
+    borderRadius: 8,
+    marginHorizontal: 18,
+    marginTop: 10,
+  },
+  itemContainer: {
+    flex: 1,
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginRight: 18,
+  },
+})
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
index ab8f98309..c79dad4df 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -37,6 +37,7 @@ export const FeedsTabBar = observer(
         'Following',
         "What's hot",
         ...store.me.savedFeeds.listOfPinnedFeedNames,
+        'My feeds',
       ],
       [store.me.savedFeeds.listOfPinnedFeedNames],
     )
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 70816a306..9be4a4794 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -14,6 +14,7 @@ import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
 import {FeedsTabBar} from '../com/pager/FeedsTabBar'
 import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {FAB} from '../com/util/fab/FAB'
+import {SavedFeeds} from 'view/com/feeds/SavedFeeds'
 import {useStores} from 'state/index'
 import {s} from 'lib/styles'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
@@ -115,14 +116,19 @@ export const HomeScreen = withAuthRequired(
         {store.me.savedFeeds.pinned.map((f, index) => {
           return (
             <FeedPage
-              key={String(2 + index + 1)}
-              testID="customFeed"
+              key={String(3 + index)}
+              testID="customFeedPage"
               isPageFocused={selectedPage === 2 + index}
               feed={new PostsFeedModel(store, 'custom', {feed: f.uri})}
               renderEmptyState={renderFollowingEmptyState}
             />
           )
         })}
+        <SavedFeeds
+          key={String(3 + store.me.savedFeeds.pinned.length)}
+          headerOffset={HEADER_OFFSET}
+          isPageFocused={selectedPage === 2 + store.me.savedFeeds.pinned.length}
+        />
       </Pager>
     )
   }),