diff options
-rw-r--r-- | src/state/models/me.ts | 7 | ||||
-rw-r--r-- | src/state/models/ui/preferences.ts | 58 | ||||
-rw-r--r-- | src/state/models/ui/saved-feeds.ts | 226 | ||||
-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 |
8 files changed, 278 insertions, 240 deletions
diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 9b2b96832..815044857 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -69,7 +69,6 @@ export class MeModel { displayName: this.displayName, description: this.description, avatar: this.avatar, - savedFeeds: this.savedFeeds.serialize(), } } @@ -91,9 +90,6 @@ export class MeModel { if (hasProp(v, 'avatar') && typeof v.avatar === 'string') { avatar = v.avatar } - if (hasProp(v, 'savedFeeds') && isObj(v.savedFeeds)) { - this.savedFeeds.hydrate(v.savedFeeds) - } if (did && handle) { this.did = did this.handle = handle @@ -118,7 +114,7 @@ export class MeModel { /* dont await */ this.notifications.setup().catch(e => { this.rootStore.log.error('Failed to setup notifications model', e) }) - /* dont await */ this.savedFeeds.refresh() + /* dont await */ this.savedFeeds.refresh(true) this.rootStore.emitSessionLoaded() await this.fetchInviteCodes() await this.fetchAppPasswords() @@ -128,6 +124,7 @@ export class MeModel { } async updateIfNeeded() { + /* dont await */ this.savedFeeds.refresh(true) if (Date.now() - this.lastProfileStateUpdate > PROFILE_UPDATE_INTERVAL) { this.rootStore.log.debug('Updating me profile information') this.lastProfileStateUpdate = Date.now() diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index 1471420fc..05a1eb128 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -25,6 +25,7 @@ const LABEL_GROUPS = [ 'spam', 'impersonation', ] +const VISIBILITY_VALUES = ['show', 'warn', 'hide'] export class LabelPreferencesModel { nsfw: LabelPreference = 'hide' @@ -45,6 +46,7 @@ export class PreferencesModel { contentLanguages: string[] = deviceLocales?.map?.(locale => locale.languageCode) || [] contentLabels = new LabelPreferencesModel() + pinnedFeeds: string[] = [] constructor(public rootStore: RootStoreModel) { makeAutoObservable(this, {}, {autoBind: true}) @@ -54,6 +56,7 @@ export class PreferencesModel { return { contentLanguages: this.contentLanguages, contentLabels: this.contentLabels, + pinnedFeeds: this.pinnedFeeds, } } @@ -72,6 +75,13 @@ export class PreferencesModel { // default to the device languages this.contentLanguages = deviceLocales.map(locale => locale.languageCode) } + if ( + hasProp(v, 'pinnedFeeds') && + Array.isArray(v.pinnedFeeds) && + typeof v.pinnedFeeds.every(item => typeof item === 'string') + ) { + this.pinnedFeeds = v.pinnedFeeds + } } } @@ -88,9 +98,18 @@ export class PreferencesModel { AppBskyActorDefs.isContentLabelPref(pref) && AppBskyActorDefs.validateAdultContentPref(pref).success ) { - if (LABEL_GROUPS.includes(pref.label)) { - this.contentLabels[pref.label] = pref.visibility + if ( + LABEL_GROUPS.includes(pref.label) && + VISIBILITY_VALUES.includes(pref.visibility) + ) { + this.contentLabels[pref.label as keyof LabelPreferencesModel] = + pref.visibility as LabelPreference } + } else if ( + AppBskyActorDefs.isPinnedFeedsPref(pref) && + AppBskyActorDefs.validatePinnedFeedsPref(pref).success + ) { + this.pinnedFeeds = pref.feeds } } }) @@ -200,4 +219,39 @@ export class PreferencesModel { } return res } + + async setPinnedFeeds(v: string[]) { + const old = this.pinnedFeeds + this.pinnedFeeds = v + try { + await this.update((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.find( + pref => + AppBskyActorDefs.isPinnedFeedsPref(pref) && + AppBskyActorDefs.validatePinnedFeedsPref(pref).success, + ) + if (existing) { + existing.feeds = v + } else { + prefs.push({ + $type: 'app.bsky.actor.defs#pinnedFeedsPref', + feeds: v, + }) + } + }) + } catch (e) { + runInAction(() => { + this.pinnedFeeds = old + }) + throw e + } + } + + async addPinnedFeed(v: string) { + return this.setPinnedFeeds([...this.pinnedFeeds, v]) + } + + async removePinnedFeed(v: string) { + return this.setPinnedFeeds(this.pinnedFeeds.filter(uri => uri !== v)) + } } diff --git a/src/state/models/ui/saved-feeds.ts b/src/state/models/ui/saved-feeds.ts index d68664c2d..f500aef2e 100644 --- a/src/state/models/ui/saved-feeds.ts +++ b/src/state/models/ui/saved-feeds.ts @@ -1,12 +1,11 @@ import {makeAutoObservable, runInAction} from 'mobx' -import {AppBskyFeedGetSavedFeeds as GetSavedFeeds} from '@atproto/api' +import {AppBskyFeedDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' import {bundleAsync} from 'lib/async/bundle' import {cleanError} from 'lib/strings/errors' import {CustomFeedModel} from '../feeds/custom-feed' -import {hasProp, isObj} from 'lib/type-guards' -const PAGE_SIZE = 30 +const PAGE_SIZE = 100 export class SavedFeedsModel { // state @@ -14,12 +13,9 @@ export class SavedFeedsModel { isRefreshing = false hasLoaded = false error = '' - hasMore = true - loadMoreCursor?: string // data feeds: CustomFeedModel[] = [] - pinned: CustomFeedModel[] = [] constructor(public rootStore: RootStoreModel) { makeAutoObservable( @@ -31,24 +27,6 @@ export class SavedFeedsModel { ) } - serialize() { - return { - pinned: this.pinned.map(f => f.serialize()), - } - } - - hydrate(v: unknown) { - if (isObj(v)) { - if (hasProp(v, 'pinned')) { - const pinnedSerialized = (v as any).pinned as string[] - const pinnedDeserialized = pinnedSerialized.map( - (s: string) => new CustomFeedModel(this.rootStore, JSON.parse(s)), - ) - this.pinned = pinnedDeserialized - } - } - } - get hasContent() { return this.feeds.length > 0 } @@ -61,149 +39,121 @@ export class SavedFeedsModel { return this.hasLoaded && !this.hasContent } - get numFeeds() { - return this.feeds.length + get pinned() { + return this.rootStore.preferences.pinnedFeeds + .map(uri => this.feeds.find(f => f.uri === uri) as CustomFeedModel) + .filter(Boolean) } get unpinned() { - return this.feeds.filter( - f => !this.pinned.find(p => p.data.uri === f.data.uri), - ) - } - - get feedNames() { - return this.feeds.map(f => f.displayName) + return this.feeds.filter(f => !this.isPinned(f)) } get pinnedFeedNames() { return this.pinned.map(f => f.displayName) } - togglePinnedFeed(feed: CustomFeedModel) { - if (!this.isPinned(feed)) { - this.pinned = [...this.pinned, feed] - } else { - this.removePinnedFeed(feed.data.uri) - } - } - - removePinnedFeed(uri: string) { - this.pinned = this.pinned.filter(f => f.data.uri !== uri) - } - - reorderPinnedFeeds(temp: CustomFeedModel[]) { - this.pinned = temp.filter(item => this.isPinned(item)) - } - - isPinned(feed: CustomFeedModel) { - return this.pinned.find(f => f.data.uri === feed.data.uri) ? true : false - } - - movePinnedItem(item: CustomFeedModel, direction: 'up' | 'down') { - if (this.pinned.length < 2) { - throw new Error('Array must have at least 2 items') - } - const index = this.pinned.indexOf(item) - if (index === -1) { - throw new Error('Item not found in array') - } - - const len = this.pinned.length - - runInAction(() => { - if (direction === 'up') { - if (index === 0) { - // Remove the item from the first place and put it at the end - this.pinned.push(this.pinned.shift()!) - } else { - // Swap the item with the one before it - const temp = this.pinned[index] - this.pinned[index] = this.pinned[index - 1] - this.pinned[index - 1] = temp - } - } else if (direction === 'down') { - if (index === len - 1) { - // Remove the item from the last place and put it at the start - this.pinned.unshift(this.pinned.pop()!) - } else { - // Swap the item with the one after it - const temp = this.pinned[index] - this.pinned[index] = this.pinned[index + 1] - this.pinned[index + 1] = temp - } - } - // this.pinned = [...this.pinned] - }) - } - // public api // = - async refresh(quietRefresh = false) { - return this.loadMore(true, quietRefresh) - } - clear() { this.isLoading = false this.isRefreshing = false this.hasLoaded = false this.error = '' - this.hasMore = true - this.loadMoreCursor = undefined this.feeds = [] } - loadMore = bundleAsync( - async (replace: boolean = false, quietRefresh = false) => { - if (!replace && !this.hasMore) { - return - } - this._xLoading(replace && !quietRefresh) - try { + refresh = bundleAsync(async (quietRefresh = false) => { + this._xLoading(!quietRefresh) + try { + let feeds: AppBskyFeedDefs.GeneratorView[] = [] + let cursor + for (let i = 0; i < 100; i++) { const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({ limit: PAGE_SIZE, - cursor: replace ? undefined : this.loadMoreCursor, + cursor, }) - if (replace) { - this._replaceAll(res) - } else { - this._appendAll(res) + feeds = feeds.concat(res.data.feeds) + cursor = res.data.cursor + if (!cursor) { + break } - this._xIdle() - } catch (e: any) { - this._xIdle(e) } - }, - ) - - removeFeed(uri: string) { - this.feeds = this.feeds.filter(f => f.data.uri !== uri) - } - - addFeed(algoItem: CustomFeedModel) { - this.feeds.push(new CustomFeedModel(this.rootStore, algoItem.data)) - } + runInAction(() => { + this.feeds = feeds.map(f => new CustomFeedModel(this.rootStore, f)) + }) + this._xIdle() + } catch (e: any) { + this._xIdle(e) + } + }) - async save(algoItem: CustomFeedModel) { + async save(feed: CustomFeedModel) { try { - await algoItem.save() - this.addFeed(algoItem) + await feed.save() + runInAction(() => { + this.feeds = [ + ...this.feeds, + new CustomFeedModel(this.rootStore, feed.data), + ] + }) } catch (e: any) { this.rootStore.log.error('Failed to save feed', e) } } - async unsave(algoItem: CustomFeedModel) { - const uri = algoItem.uri + async unsave(feed: CustomFeedModel) { + const uri = feed.uri try { - await algoItem.unsave() - this.removeFeed(uri) - this.removePinnedFeed(uri) + if (this.isPinned(feed)) { + await this.rootStore.preferences.removePinnedFeed(uri) + } + await feed.unsave() + runInAction(() => { + this.feeds = this.feeds.filter(f => f.data.uri !== uri) + }) } catch (e: any) { this.rootStore.log.error('Failed to unsave feed', e) } } + async togglePinnedFeed(feed: CustomFeedModel) { + if (!this.isPinned(feed)) { + return this.rootStore.preferences.addPinnedFeed(feed.uri) + } else { + return this.rootStore.preferences.removePinnedFeed(feed.uri) + } + } + + async reorderPinnedFeeds(feeds: CustomFeedModel[]) { + return this.rootStore.preferences.setPinnedFeeds( + feeds.filter(feed => this.isPinned(feed)).map(feed => feed.uri), + ) + } + + isPinned(feed: CustomFeedModel) { + return this.rootStore.preferences.pinnedFeeds.includes(feed.uri) + } + + async movePinnedFeed(item: CustomFeedModel, direction: 'up' | 'down') { + const pinned = this.rootStore.preferences.pinnedFeeds.slice() + const index = pinned.indexOf(item.uri) + if (index === -1) { + return + } + if (direction === 'up' && index !== 0) { + const temp = pinned[index] + pinned[index] = pinned[index - 1] + pinned[index - 1] = temp + } else if (direction === 'down' && index < pinned.length - 1) { + const temp = pinned[index] + pinned[index] = pinned[index + 1] + pinned[index + 1] = temp + } + await this.rootStore.preferences.setPinnedFeeds(pinned) + } + // state transitions // = @@ -219,23 +169,7 @@ export class SavedFeedsModel { this.hasLoaded = true this.error = cleanError(err) if (err) { - this.rootStore.log.error('Failed to fetch user followers', err) - } - } - - // helper functions - // = - - _replaceAll(res: GetSavedFeeds.Response) { - this.feeds = [] - this._appendAll(res) - } - - _appendAll(res: GetSavedFeeds.Response) { - this.loadMoreCursor = res.data.cursor - this.hasMore = !!this.loadMoreCursor - for (const f of res.data.feeds) { - this.feeds.push(new CustomFeedModel(this.rootStore, f)) + this.rootStore.log.error('Failed to fetch user feeds', err) } } } 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} |