diff --git a/src/Navigation.tsx b/src/Navigation.tsx index d4c992eb..17d80dfd 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -54,6 +54,7 @@ import {BlockedAccounts} from 'view/screens/BlockedAccounts' import {getRoutingInstrumentation} from 'lib/sentry' import {SavedFeeds} from './view/screens/SavedFeeds' import {CustomFeed} from './view/screens/CustomFeed' +import {PinnedFeeds} from 'view/screens/PinnedFeeds' const navigationRef = createNavigationContainerRef() @@ -94,6 +95,7 @@ function commonScreens(Stack: typeof HomeTab) { + diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 77ed58cc..12ff2707 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -21,6 +21,7 @@ export type CommonNavigatorParams = { CopyrightPolicy: undefined AppPasswords: undefined SavedFeeds: undefined + PinnedFeeds: undefined CustomFeed: {name?: string; rkey: string} MutedAccounts: undefined BlockedAccounts: undefined diff --git a/src/routes.ts b/src/routes.ts index 2cdaa28b..6998226c 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -15,6 +15,7 @@ export const router = new Router({ Log: '/sys/log', AppPasswords: '/settings/app-passwords', SavedFeeds: '/settings/saved-feeds', + PinnedFeeds: '/settings/pinned-feeds', CustomFeed: '/profile/:name/feed/:rkey', MutedAccounts: '/settings/muted-accounts', BlockedAccounts: '/settings/blocked-accounts', diff --git a/src/view/com/algos/AlgoItem.tsx b/src/view/com/algos/AlgoItem.tsx index 6fbbd0df..b28545c1 100644 --- a/src/view/com/algos/AlgoItem.tsx +++ b/src/view/com/algos/AlgoItem.tsx @@ -24,13 +24,11 @@ const AlgoItem = observer( item, style, showBottom = true, - onLongPress, reloadOnFocus = false, }: { item: AlgoItemModel style?: StyleProp showBottom?: boolean - onLongPress?: () => void reloadOnFocus?: boolean }) => { const store = useStores() @@ -54,7 +52,6 @@ const AlgoItem = observer( rkey: item.data.uri, }) }} - onLongPress={onLongPress} key={item.data.uri}> @@ -64,8 +61,9 @@ const AlgoItem = observer( {item.data.displayName ?? 'Feed name'} - - {item.data.description ?? 'Feed description'} + + {item.data.description ?? + "Explore our Feed for the latest updates and insights! Dive into a world of intriguing articles, trending news, and exciting stories that cover a wide range of topics. From technology breakthroughs to lifestyle tips, there's something here for everyone. Stay informed and get inspired with us. Join the conversation now!"} diff --git a/src/view/com/algos/SavedFeedItem.tsx b/src/view/com/algos/SavedFeedItem.tsx new file mode 100644 index 00000000..bb4ec10b --- /dev/null +++ b/src/view/com/algos/SavedFeedItem.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import {View, TouchableOpacity, StyleSheet} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {colors} from 'lib/styles' +import {observer} from 'mobx-react-lite' +import {AlgoItemModel} from 'state/models/feeds/algo/algo-item' +import {SavedFeedsModel} from 'state/models/feeds/algo/saved' +import AlgoItem from './AlgoItem' + +export const SavedFeedItem = observer( + ({item, savedFeeds}: {item: AlgoItemModel; savedFeeds: SavedFeedsModel}) => { + const isPinned = savedFeeds.isPinned(item) + + return ( + + + { + savedFeeds.togglePinnedFeed(item) + console.log('pinned', savedFeeds.pinned) + console.log('isPinned', savedFeeds.isPinned(item)) + }}> + + + + ) + }, +) + +const styles = StyleSheet.create({ + itemContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + marginRight: 18, + }, + item: { + borderTopWidth: 0, + }, +}) diff --git a/src/view/screens/PinnedFeeds.tsx b/src/view/screens/PinnedFeeds.tsx new file mode 100644 index 00000000..2a0e3def --- /dev/null +++ b/src/view/screens/PinnedFeeds.tsx @@ -0,0 +1,134 @@ +import React, {useCallback, useMemo} from 'react' +import { + RefreshControl, + StyleSheet, + View, + ActivityIndicator, + Pressable, +} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {useAnalytics} from 'lib/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {CommonNavigatorParams} from 'lib/routes/types' +import {observer} from 'mobx-react-lite' +import {useStores} from 'state/index' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {CenteredView} from 'view/com/util/Views' +import {Text} from 'view/com/util/text/Text' +import {isDesktopWeb} from 'platform/detection' +import {s} from 'lib/styles' +import DraggableFlatList, { + ShadowDecorator, + ScaleDecorator, +} from 'react-native-draggable-flatlist' +import {SavedFeedItem} from 'view/com/algos/SavedFeedItem' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' + +type Props = NativeStackScreenProps + +export const PinnedFeeds = withAuthRequired( + observer(({}: Props) => { + // hooks for global items + const pal = usePalette('default') + const rootStore = useStores() + const {screen} = useAnalytics() + + // hooks for local + const savedFeeds = useMemo(() => rootStore.me.savedFeeds, [rootStore]) + useFocusEffect( + useCallback(() => { + screen('SavedFeeds') + rootStore.shell.setMinimalShellMode(false) + savedFeeds.refresh() + }, [screen, rootStore, savedFeeds]), + ) + const _ListEmptyComponent = () => { + return ( + + + You don't have any pinned feeds. To pin a feed, go back to the Saved + Feeds screen and click the pin icon! + + + ) + } + const _ListFooterComponent = () => { + return ( + + {savedFeeds.isLoading && } + + ) + } + + return ( + + + item.data.uri} + refreshing={savedFeeds.isRefreshing} + refreshControl={ + savedFeeds.refresh()} + tintColor={pal.colors.text} + titleColor={pal.colors.text} + /> + } + renderItem={({item, drag}) => ( + + + + + + + + + )} + initialNumToRender={10} + ListFooterComponent={_ListFooterComponent} + ListEmptyComponent={_ListEmptyComponent} + extraData={savedFeeds.isLoading} + onDragEnd={({data}) => savedFeeds.reorderPinnedFeeds(data)} + // @ts-ignore our .web version only -prf + desktopFixedHeight + /> + + ) + }), +) + +const styles = StyleSheet.create({ + footer: { + paddingVertical: 20, + }, + empty: { + paddingHorizontal: 20, + paddingVertical: 20, + borderRadius: 16, + marginHorizontal: 24, + marginTop: 10, + }, + itemContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + marginLeft: 18, + }, + item: { + borderTopWidth: 0, + }, + icon: {marginRight: 10}, +}) diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 65ffdb23..8403efc6 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -6,6 +6,7 @@ import { ActivityIndicator, FlatList, TouchableOpacity, + ScrollView, } from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' @@ -14,21 +15,21 @@ import {usePalette} from 'lib/hooks/usePalette' import {CommonNavigatorParams} from 'lib/routes/types' import {observer} from 'mobx-react-lite' import {useStores} from 'state/index' -import AlgoItem from 'view/com/algos/AlgoItem' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from 'view/com/util/ViewHeader' import {CenteredView} from 'view/com/util/Views' import {Text} from 'view/com/util/text/Text' import {isDesktopWeb} from 'platform/detection' -import {colors, s} from 'lib/styles' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {AlgoItemModel} from 'state/models/feeds/algo/algo-item' +import {s} from 'lib/styles' import {SavedFeedsModel} from 'state/models/feeds/algo/saved' +import {Link} from 'view/com/util/Link' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {SavedFeedItem} from 'view/com/algos/SavedFeedItem' type Props = NativeStackScreenProps export const SavedFeeds = withAuthRequired( - observer(({}: Props) => { + observer(({navigation}: Props) => { // hooks for global items const pal = usePalette('default') const rootStore = useStores() @@ -87,6 +88,12 @@ export const SavedFeeds = withAuthRequired( )} initialNumToRender={10} + ListHeaderComponent={() => ( + + )} ListFooterComponent={_ListFooterComponent} ListEmptyComponent={_ListEmptyComponent} extraData={savedFeeds.isLoading} @@ -98,31 +105,53 @@ export const SavedFeeds = withAuthRequired( }), ) -const SavedFeedItem = observer( - ({item, savedFeeds}: {item: AlgoItemModel; savedFeeds: SavedFeedsModel}) => { - const isPinned = savedFeeds.isPinned(item) - +const ListHeaderComponent = observer( + ({ + savedFeeds, + navigation, + }: { + savedFeeds: SavedFeedsModel + navigation: Props['navigation'] + }) => { return ( - - - { - savedFeeds.togglePinnedFeed(item) - console.log('pinned', savedFeeds.pinned) - console.log('isPinned', savedFeeds.isPinned(item)) - }}> - - + + {savedFeeds.pinned.length > 0 ? ( + + + Pinned Feeds + + Edit + + + + + {savedFeeds.pinned.map(item => { + return ( + { + navigation.navigate('CustomFeed', { + rkey: item.data.uri, + name: item.data.displayName, + }) + }} + style={styles.pinnedItem}> + + + {item.data.displayName ?? + `${item.data.creator.displayName}'s feed`} + + + ) + })} + + + ) : null} + + All Saved Feeds ) }, @@ -139,12 +168,14 @@ const styles = StyleSheet.create({ marginHorizontal: 24, marginTop: 10, }, - itemContainer: { - flexDirection: 'row', + headerContainer: {paddingHorizontal: 18}, + pinnedContainer: {marginBottom: 18, gap: 18}, + pinnedHeader: {flexDirection: 'row', justifyContent: 'space-between'}, + pinnedItem: { + flex: 1, alignItems: 'center', marginRight: 18, + maxWidth: 100, }, - item: { - borderTopWidth: 0, - }, + editPinned: {textDecorationLine: 'underline'}, })