about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-05-18 11:51:25 -0500
committerPaul Frazee <pfrazee@gmail.com>2023-05-18 11:51:25 -0500
commit7691fe4f481bf08c711cf92da91b2c204d121a7f (patch)
treeb73b64c6f051c79aae922642897e1a8fea51aaac /src/view
parentd88c27a41995c181a38c01248fe01f853ba83366 (diff)
downloadvoidsky-7691fe4f481bf08c711cf92da91b2c204d121a7f.tar.zst
Store/sync pinned feeds on the server
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/feeds/CustomFeed.tsx22
-rw-r--r--src/view/com/feeds/SavedFeeds.tsx6
-rw-r--r--src/view/com/pager/Pager.web.tsx99
-rw-r--r--src/view/screens/Home.tsx16
-rw-r--r--src/view/screens/SavedFeeds.tsx84
5 files changed, 140 insertions, 87 deletions
diff --git a/src/view/com/feeds/CustomFeed.tsx b/src/view/com/feeds/CustomFeed.tsx
index 911c33da4..d4e843b67 100644
--- a/src/view/com/feeds/CustomFeed.tsx
+++ b/src/view/com/feeds/CustomFeed.tsx
@@ -39,20 +39,30 @@ export const CustomFeed = observer(
     const pal = usePalette('default')
     const navigation = useNavigation<NavigationProp>()
 
-    const onToggleSaved = React.useCallback(() => {
+    const onToggleSaved = React.useCallback(async () => {
       if (item.data.viewer?.saved) {
         store.shell.openModal({
           name: 'confirm',
           title: 'Remove from my feeds',
           message: `Remove ${item.displayName} from my feeds?`,
-          onPressConfirm: () => {
-            store.me.savedFeeds.unsave(item)
-            Toast.show('Removed from my feeds')
+          onPressConfirm: async () => {
+            try {
+              await store.me.savedFeeds.unsave(item)
+              Toast.show('Removed from my feeds')
+            } catch (e) {
+              Toast.show('There was an issue contacting your server')
+              store.log.error('Failed to unsave feed', {e})
+            }
           },
         })
       } else {
-        store.me.savedFeeds.save(item)
-        Toast.show('Added to my feeds')
+        try {
+          await store.me.savedFeeds.save(item)
+          Toast.show('Added to my feeds')
+        } catch (e) {
+          Toast.show('There was an issue contacting your server')
+          store.log.error('Failed to save feed', {e})
+        }
       }
     }, [store, item])
 
diff --git a/src/view/com/feeds/SavedFeeds.tsx b/src/view/com/feeds/SavedFeeds.tsx
index 7135fdf0a..1cb109a43 100644
--- a/src/view/com/feeds/SavedFeeds.tsx
+++ b/src/view/com/feeds/SavedFeeds.tsx
@@ -29,6 +29,10 @@ export const SavedFeeds = observer(
       }
     }, [store, isPageFocused])
 
+    const onRefresh = useCallback(() => {
+      store.me.savedFeeds.refresh()
+    }, [store])
+
     const renderListEmptyComponent = useCallback(() => {
       return (
         <View
@@ -73,7 +77,7 @@ export const SavedFeeds = observer(
         refreshControl={
           <RefreshControl
             refreshing={store.me.savedFeeds.isRefreshing}
-            onRefresh={() => store.me.savedFeeds.refresh()}
+            onRefresh={onRefresh}
             tintColor={pal.colors.text}
             titleColor={pal.colors.text}
             progressViewOffset={headerOffset}
diff --git a/src/view/com/pager/Pager.web.tsx b/src/view/com/pager/Pager.web.tsx
index 107497f6f..7be2b11ec 100644
--- a/src/view/com/pager/Pager.web.tsx
+++ b/src/view/com/pager/Pager.web.tsx
@@ -1,12 +1,9 @@
 import React from 'react'
-import {Animated, View} from 'react-native'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+import {View} from 'react-native'
 import {s} from 'lib/styles'
 
 export interface RenderTabBarFnProps {
   selectedPage: number
-  position: Animated.Value
-  offset: Animated.Value
   onSelect?: (index: number) => void
 }
 export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
@@ -17,53 +14,51 @@ interface Props {
   renderTabBar: RenderTabBarFn
   onPageSelected?: (index: number) => void
 }
-export const Pager = ({
-  children,
-  tabBarPosition = 'top',
-  initialPage = 0,
-  renderTabBar,
-  onPageSelected,
-}: React.PropsWithChildren<Props>) => {
-  const [selectedPage, setSelectedPage] = React.useState(initialPage)
-  const position = useAnimatedValue(0)
-  const offset = useAnimatedValue(0)
+export const Pager = React.forwardRef(
+  (
+    {
+      children,
+      tabBarPosition = 'top',
+      initialPage = 0,
+      renderTabBar,
+      onPageSelected,
+    }: React.PropsWithChildren<Props>,
+    ref,
+  ) => {
+    const [selectedPage, setSelectedPage] = React.useState(initialPage)
 
-  const onTabBarSelect = React.useCallback(
-    (index: number) => {
-      setSelectedPage(index)
-      onPageSelected?.(index)
-      Animated.timing(position, {
-        toValue: index,
-        duration: 200,
-        useNativeDriver: true,
-      }).start()
-    },
-    [setSelectedPage, onPageSelected, position],
-  )
+    React.useImperativeHandle(ref, () => ({
+      setPage: (index: number) => setSelectedPage(index),
+    }))
 
-  return (
-    <View>
-      {tabBarPosition === 'top' &&
-        renderTabBar({
-          selectedPage,
-          position,
-          offset,
-          onSelect: onTabBarSelect,
-        })}
-      {React.Children.map(children, (child, i) => (
-        <View
-          style={selectedPage === i ? undefined : s.hidden}
-          key={`page-${i}`}>
-          {child}
-        </View>
-      ))}
-      {tabBarPosition === 'bottom' &&
-        renderTabBar({
-          selectedPage,
-          position,
-          offset,
-          onSelect: onTabBarSelect,
-        })}
-    </View>
-  )
-}
+    const onTabBarSelect = React.useCallback(
+      (index: number) => {
+        setSelectedPage(index)
+        onPageSelected?.(index)
+      },
+      [setSelectedPage, onPageSelected],
+    )
+
+    return (
+      <View>
+        {tabBarPosition === 'top' &&
+          renderTabBar({
+            selectedPage,
+            onSelect: onTabBarSelect,
+          })}
+        {React.Children.map(children, (child, i) => (
+          <View
+            style={selectedPage === i ? undefined : s.hidden}
+            key={`page-${i}`}>
+            {child}
+          </View>
+        ))}
+        {tabBarPosition === 'bottom' &&
+          renderTabBar({
+            selectedPage,
+            onSelect: onTabBarSelect,
+          })}
+      </View>
+    )
+  },
+)
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 644182126..54cec3b31 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -4,6 +4,7 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'
 import {AppBskyFeedGetFeed as GetCustomFeed} from '@atproto/api'
 import {observer} from 'mobx-react-lite'
 import useAppState from 'react-native-appstate-hook'
+import isEqual from 'lodash.isequal'
 import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
 import {PostsFeedModel} from 'state/models/feeds/posts'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
@@ -44,15 +45,26 @@ export const HomeScreen = withAuthRequired(
     }, [store])
 
     React.useEffect(() => {
+      const {pinned} = store.me.savedFeeds
+      if (
+        isEqual(
+          pinned.map(p => p.uri),
+          customFeeds.map(f => (f.params as GetCustomFeed.QueryParams).feed),
+        )
+      ) {
+        // no changes
+        return
+      }
+
       const feeds = []
-      for (const feed of store.me.savedFeeds.pinned) {
+      for (const feed of pinned) {
         const model = new PostsFeedModel(store, 'custom', {feed: feed.uri})
         model.setup()
         feeds.push(model)
       }
       pagerRef.current?.setPage(0)
       setCustomFeeds(feeds)
-    }, [store, store.me.savedFeeds.pinned, setCustomFeeds])
+    }, [store, store.me.savedFeeds.pinned, customFeeds, setCustomFeeds])
 
     React.useEffect(() => {
       // refresh whats hot when lang preferences change
diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx
index c2723f694..613e42fbf 100644
--- a/src/view/screens/SavedFeeds.tsx
+++ b/src/view/screens/SavedFeeds.tsx
@@ -27,26 +27,26 @@ import DraggableFlatList, {
 import {CustomFeed} from 'view/com/feeds/CustomFeed'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {CustomFeedModel} from 'state/models/feeds/custom-feed'
+import * as Toast from 'view/com/util/Toast'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'>
 
 export const SavedFeeds = withAuthRequired(
   observer(({}: Props) => {
-    // hooks for global items
     const pal = usePalette('default')
-    const rootStore = useStores()
+    const store = useStores()
     const {screen} = useAnalytics()
 
-    // hooks for local
-    const savedFeeds = useMemo(() => rootStore.me.savedFeeds, [rootStore])
+    const savedFeeds = useMemo(() => store.me.savedFeeds, [store])
     useFocusEffect(
       useCallback(() => {
         screen('SavedFeeds')
-        rootStore.shell.setMinimalShellMode(false)
+        store.shell.setMinimalShellMode(false)
         savedFeeds.refresh()
-      }, [screen, rootStore, savedFeeds]),
+      }, [screen, store, savedFeeds]),
     )
-    const _ListEmptyComponent = () => {
+
+    const renderListEmptyComponent = useCallback(() => {
       return (
         <View
           style={[
@@ -56,19 +56,33 @@ export const SavedFeeds = withAuthRequired(
             styles.empty,
           ]}>
           <Text type="lg" style={[pal.text]}>
-            You don't have any pinned feeds. To pin a feed, go back to the Saved
-            Feeds screen and click the pin icon!
+            You don't have any saved feeds.
           </Text>
         </View>
       )
-    }
-    const _ListFooterComponent = () => {
+    }, [pal])
+
+    const renderListFooterComponent = useCallback(() => {
       return (
         <View style={styles.footer}>
           {savedFeeds.isLoading && <ActivityIndicator />}
         </View>
       )
-    }
+    }, [savedFeeds])
+
+    const onRefresh = useCallback(() => savedFeeds.refresh(), [savedFeeds])
+
+    const onDragEnd = useCallback(
+      async ({data}) => {
+        try {
+          await savedFeeds.reorderPinnedFeeds(data)
+        } catch (e) {
+          Toast.show('There was an issue contacting the server')
+          store.log.error('Failed to save pinned feed order', {e})
+        }
+      },
+      [savedFeeds, store],
+    )
 
     return (
       <CenteredView
@@ -90,17 +104,17 @@ export const SavedFeeds = withAuthRequired(
           refreshControl={
             <RefreshControl
               refreshing={savedFeeds.isRefreshing}
-              onRefresh={() => savedFeeds.refresh()}
+              onRefresh={onRefresh}
               tintColor={pal.colors.text}
               titleColor={pal.colors.text}
             />
           }
           renderItem={({item, drag}) => <ListItem item={item} drag={drag} />}
           initialNumToRender={10}
-          ListFooterComponent={_ListFooterComponent}
-          ListEmptyComponent={_ListEmptyComponent}
+          ListFooterComponent={renderListFooterComponent}
+          ListEmptyComponent={renderListEmptyComponent}
           extraData={savedFeeds.isLoading}
-          onDragEnd={({data}) => savedFeeds.reorderPinnedFeeds(data)}
+          onDragEnd={onDragEnd}
         />
       </CenteredView>
     )
@@ -110,13 +124,35 @@ export const SavedFeeds = withAuthRequired(
 const ListItem = observer(
   ({item, drag}: {item: CustomFeedModel; drag: () => void}) => {
     const pal = usePalette('default')
-    const rootStore = useStores()
-    const savedFeeds = useMemo(() => rootStore.me.savedFeeds, [rootStore])
+    const store = useStores()
+    const savedFeeds = useMemo(() => store.me.savedFeeds, [store])
     const isPinned = savedFeeds.isPinned(item)
+
     const onTogglePinned = useCallback(
-      () => savedFeeds.togglePinnedFeed(item),
-      [savedFeeds, item],
+      () =>
+        savedFeeds.togglePinnedFeed(item).catch(e => {
+          Toast.show('There was an issue contacting the server')
+          store.log.error('Failed to toggle pinned feed', {e})
+        }),
+      [savedFeeds, item, store],
+    )
+    const onPressUp = useCallback(
+      () =>
+        savedFeeds.movePinnedFeed(item, 'up').catch(e => {
+          Toast.show('There was an issue contacting the server')
+          store.log.error('Failed to set pinned feed order', {e})
+        }),
+      [store, savedFeeds, item],
     )
+    const onPressDown = useCallback(
+      () =>
+        savedFeeds.movePinnedFeed(item, 'down').catch(e => {
+          Toast.show('There was an issue contacting the server')
+          store.log.error('Failed to set pinned feed order', {e})
+        }),
+      [store, savedFeeds, item],
+    )
+
     return (
       <ScaleDecorator>
         <ShadowDecorator>
@@ -128,9 +164,7 @@ const ListItem = observer(
               <View style={styles.webArrowButtonsContainer}>
                 <TouchableOpacity
                   accessibilityRole="button"
-                  onPress={() => {
-                    savedFeeds.movePinnedItem(item, 'up')
-                  }}>
+                  onPress={onPressUp}>
                   <FontAwesomeIcon
                     icon="arrow-up"
                     size={12}
@@ -139,9 +173,7 @@ const ListItem = observer(
                 </TouchableOpacity>
                 <TouchableOpacity
                   accessibilityRole="button"
-                  onPress={() => {
-                    savedFeeds.movePinnedItem(item, 'down')
-                  }}>
+                  onPress={onPressDown}>
                   <FontAwesomeIcon
                     icon="arrow-down"
                     size={12}