diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-05-18 11:51:25 -0500 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2023-05-18 11:51:25 -0500 |
commit | 7691fe4f481bf08c711cf92da91b2c204d121a7f (patch) | |
tree | b73b64c6f051c79aae922642897e1a8fea51aaac /src/view | |
parent | d88c27a41995c181a38c01248fe01f853ba83366 (diff) | |
download | voidsky-7691fe4f481bf08c711cf92da91b2c204d121a7f.tar.zst |
Store/sync pinned feeds on the server
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/feeds/CustomFeed.tsx | 22 | ||||
-rw-r--r-- | src/view/com/feeds/SavedFeeds.tsx | 6 | ||||
-rw-r--r-- | src/view/com/pager/Pager.web.tsx | 99 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 16 | ||||
-rw-r--r-- | src/view/screens/SavedFeeds.tsx | 84 |
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} |