diff options
-rw-r--r-- | src/state/models/ui/saved-feeds.ts | 20 | ||||
-rw-r--r-- | src/view/com/feeds/CustomFeed.tsx | 77 | ||||
-rw-r--r-- | src/view/com/feeds/SavedFeedItem.tsx | 38 | ||||
-rw-r--r-- | src/view/com/feeds/SavedFeeds.tsx | 61 | ||||
-rw-r--r-- | src/view/com/pager/FeedsTabBar.web.tsx | 8 | ||||
-rw-r--r-- | src/view/com/pager/FeedsTabBarMobile.tsx | 4 | ||||
-rw-r--r-- | src/view/screens/PinnedFeeds.tsx | 34 | ||||
-rw-r--r-- | src/view/screens/ProfileCustomFeed.tsx | 18 |
8 files changed, 119 insertions, 141 deletions
diff --git a/src/state/models/ui/saved-feeds.ts b/src/state/models/ui/saved-feeds.ts index bae98fc84..50bb1b871 100644 --- a/src/state/models/ui/saved-feeds.ts +++ b/src/state/models/ui/saved-feeds.ts @@ -61,22 +61,22 @@ export class SavedFeedsModel { return this.hasLoaded && !this.hasContent } - get numOfFeeds() { + get numFeeds() { return this.feeds.length } - get listOfFeedNames() { - return this.feeds.map(f => f.displayName) + get unpinned() { + return this.feeds.filter( + f => !this.pinned.find(p => p.data.uri === f.data.uri), + ) } - get listOfPinnedFeedNames() { - return this.pinned.map(f => f.displayName) + get feedNames() { + return this.feeds.map(f => f.displayName) } - get savedFeedsWithoutPinned() { - return this.feeds.filter( - f => !this.pinned.find(p => p.data.uri === f.data.uri), - ) + get pinnedFeedNames() { + return this.pinned.map(f => f.displayName) } togglePinnedFeed(feed: CustomFeedModel) { @@ -92,7 +92,7 @@ export class SavedFeedsModel { } reorderPinnedFeeds(temp: CustomFeedModel[]) { - this.pinned = temp + this.pinned = temp.filter(item => this.isPinned(item)) } isPinned(feed: CustomFeedModel) { diff --git a/src/view/com/feeds/CustomFeed.tsx b/src/view/com/feeds/CustomFeed.tsx index 5440a8e8f..5201ca848 100644 --- a/src/view/com/feeds/CustomFeed.tsx +++ b/src/view/com/feeds/CustomFeed.tsx @@ -1,16 +1,17 @@ import React from 'react' import { + Pressable, StyleProp, StyleSheet, View, ViewStyle, TouchableOpacity, } from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../util/text/Text' import {usePalette} from 'lib/hooks/usePalette' import {s} from 'lib/styles' import {UserAvatar} from '../util/UserAvatar' -import {Button} from '../util/forms/Button' import {observer} from 'mobx-react-lite' import {CustomFeedModel} from 'state/models/feeds/custom-feed' import {useNavigation} from '@react-navigation/native' @@ -18,6 +19,7 @@ import {NavigationProp} from 'lib/routes/types' import {useStores} from 'state/index' import {pluralize} from 'lib/strings/helpers' import {AtUri} from '@atproto/api' +import * as Toast from 'view/com/util/Toast' export const CustomFeed = observer( ({ @@ -37,6 +39,23 @@ export const CustomFeed = observer( const pal = usePalette('default') const navigation = useNavigation<NavigationProp>() + const onToggleSaved = React.useCallback(() => { + 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') + }, + }) + } else { + store.me.savedFeeds.save(item) + Toast.show('Added to my feeds') + } + }, [store, item]) + return ( <TouchableOpacity accessibilityRole="button" @@ -62,17 +81,28 @@ export const CustomFeed = observer( </View> {showSaveBtn && ( <View> - <Button - type={item.isSaved ? 'default' : 'inverted'} - onPress={() => { - if (item.data.viewer?.saved) { - store.me.savedFeeds.unsave(item) - } else { - store.me.savedFeeds.save(item) - } - }} - label={item.data.viewer?.saved ? 'Unsave' : 'Save'} - /> + <Pressable + accessibilityRole="button" + accessibilityLabel={ + item.isSaved ? 'Remove from my feeds' : 'Add to my feeds' + } + accessibilityHint="" + onPress={onToggleSaved} + style={styles.btn}> + {item.isSaved ? ( + <FontAwesomeIcon + icon={['far', 'trash-can']} + size={19} + color={pal.colors.icon} + /> + ) : ( + <FontAwesomeIcon + icon="plus" + size={18} + color={pal.colors.link} + /> + )} + </Pressable> </View> )} </View> @@ -84,14 +114,10 @@ export const CustomFeed = observer( ) : null} {showLikes ? ( - <View style={styles.bottomContainer}> - <View style={styles.likedByContainer}> - <Text type="sm-medium" style={[pal.text, pal.textLight]}> - Liked by {item.data.likeCount || 0}{' '} - {pluralize(item.data.likeCount || 0, 'user')} - </Text> - </View> - </View> + <Text type="sm-medium" style={[pal.text, pal.textLight]}> + Liked by {item.data.likeCount || 0}{' '} + {pluralize(item.data.likeCount || 0, 'user')} + </Text> ) : null} </TouchableOpacity> ) @@ -119,14 +145,7 @@ const styles = StyleSheet.create({ flex: 1, flexWrap: 'wrap', }, - bottomContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - likedByContainer: { - flexDirection: 'row', - alignItems: 'center', - gap: 2, + btn: { + paddingVertical: 6, }, }) diff --git a/src/view/com/feeds/SavedFeedItem.tsx b/src/view/com/feeds/SavedFeedItem.tsx index 329f1811e..8a5889e47 100644 --- a/src/view/com/feeds/SavedFeedItem.tsx +++ b/src/view/com/feeds/SavedFeedItem.tsx @@ -1,41 +1,51 @@ -import React from 'react' -import {View, TouchableOpacity, StyleSheet} from 'react-native' +import React, {useCallback} from 'react' +import { + View, + TouchableOpacity, + StyleSheet, + StyleProp, + ViewStyle, +} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {colors} from 'lib/styles' import {observer} from 'mobx-react-lite' import {CustomFeedModel} from 'state/models/feeds/custom-feed' import {SavedFeedsModel} from 'state/models/ui/saved-feeds' import {CustomFeed} from './CustomFeed' +import {usePalette} from 'lib/hooks/usePalette' export const SavedFeedItem = observer( ({ item, savedFeeds, + showSaveBtn = false, + style, }: { item: CustomFeedModel savedFeeds: SavedFeedsModel + showSaveBtn?: boolean + style?: StyleProp<ViewStyle> }) => { + const pal = usePalette('default') const isPinned = savedFeeds.isPinned(item) + const onTogglePinned = useCallback( + () => savedFeeds.togglePinnedFeed(item), + [savedFeeds, item], + ) return ( - <View style={styles.itemContainer}> + <View style={[styles.itemContainer, style]}> <CustomFeed key={item.data.uri} item={item} - style={styles.item} - showSaveBtn + showSaveBtn={showSaveBtn} + style={styles.noBorder} /> - <TouchableOpacity - accessibilityRole="button" - onPress={() => { - savedFeeds.togglePinnedFeed(item) - console.log('pinned', savedFeeds.pinned) - console.log('isPinned', savedFeeds.isPinned(item)) - }}> + <TouchableOpacity accessibilityRole="button" onPress={onTogglePinned}> <FontAwesomeIcon icon="thumb-tack" size={20} - color={isPinned ? colors.blue3 : colors.gray3} + color={isPinned ? colors.blue3 : pal.colors.icon} /> </TouchableOpacity> </View> @@ -50,7 +60,7 @@ const styles = StyleSheet.create({ alignItems: 'center', marginRight: 18, }, - item: { + noBorder: { borderTopWidth: 0, }, }) diff --git a/src/view/com/feeds/SavedFeeds.tsx b/src/view/com/feeds/SavedFeeds.tsx index 66a4efecf..06c47d114 100644 --- a/src/view/com/feeds/SavedFeeds.tsx +++ b/src/view/com/feeds/SavedFeeds.tsx @@ -1,24 +1,15 @@ import React, {useEffect, useCallback} from 'react' -import { - ActivityIndicator, - FlatList, - RefreshControl, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' +import {FlatList, RefreshControl, StyleSheet, 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 {s} from 'lib/styles' import {Link} from 'view/com/util/Link' -import {CustomFeed} from 'view/com/feeds/CustomFeed' +import {CustomFeed} from './CustomFeed' export const SavedFeeds = observer( ({ @@ -69,14 +60,8 @@ export const SavedFeeds = observer( }, [pal]) const renderItem = useCallback( - ({item}) => ( - <SavedFeedItem - key={item.data.uri} - item={item} - savedFeeds={store.me.savedFeeds} - /> - ), - [store.me.savedFeeds], + ({item}) => <CustomFeed key={item.data.uri} item={item} />, + [], ) return ( @@ -109,35 +94,6 @@ export const SavedFeeds = observer( }, ) -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', @@ -154,10 +110,7 @@ const styles = StyleSheet.create({ marginHorizontal: 18, marginTop: 10, }, - itemContainer: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - marginRight: 18, + feedItem: { + borderTopWidth: 1, }, }) diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx index 6de38fa1d..d1a05b153 100644 --- a/src/view/com/pager/FeedsTabBar.web.tsx +++ b/src/view/com/pager/FeedsTabBar.web.tsx @@ -28,12 +28,8 @@ const FeedsTabBarDesktop = observer( ) => { const store = useStores() const items = useMemo( - () => [ - 'Following', - "What's hot", - ...store.me.savedFeeds.listOfPinnedFeedNames, - ], - [store.me.savedFeeds.listOfPinnedFeedNames], + () => ['Following', "What's hot", ...store.me.savedFeeds.pinnedFeedNames], + [store.me.savedFeeds.pinnedFeedNames], ) const pal = usePalette('default') const interp = useAnimatedValue(0) diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx index c79dad4df..0c40da436 100644 --- a/src/view/com/pager/FeedsTabBarMobile.tsx +++ b/src/view/com/pager/FeedsTabBarMobile.tsx @@ -36,10 +36,10 @@ export const FeedsTabBar = observer( () => [ 'Following', "What's hot", - ...store.me.savedFeeds.listOfPinnedFeedNames, + ...store.me.savedFeeds.pinnedFeedNames, 'My feeds', ], - [store.me.savedFeeds.listOfPinnedFeedNames], + [store.me.savedFeeds.pinnedFeedNames], ) return ( diff --git a/src/view/screens/PinnedFeeds.tsx b/src/view/screens/PinnedFeeds.tsx index 85f737749..a90012093 100644 --- a/src/view/screens/PinnedFeeds.tsx +++ b/src/view/screens/PinnedFeeds.tsx @@ -72,10 +72,10 @@ export const PinnedFeeds = withAuthRequired( return ( <CenteredView style={[s.flex1]}> - <ViewHeader title="Arrange Pinned Feeds" showOnDesktop /> + <ViewHeader title="Edit My Feeds" showOnDesktop /> <DraggableFlatList containerStyle={[!isDesktopWeb && s.flex1]} - data={[...savedFeeds.pinned]} // make a copy so this FlatList re-renders when pinned changes + data={[...savedFeeds.pinned, ...savedFeeds.unpinned]} // make a copy so this FlatList re-renders when pinned changes keyExtractor={item => item.data.uri} refreshing={savedFeeds.isRefreshing} refreshControl={ @@ -86,7 +86,7 @@ export const PinnedFeeds = withAuthRequired( titleColor={pal.colors.text} /> } - renderItem={({item, drag}) => <PinnedItem item={item} drag={drag} />} + renderItem={({item, drag}) => <ListItem item={item} drag={drag} />} initialNumToRender={10} ListFooterComponent={_ListFooterComponent} ListEmptyComponent={_ListEmptyComponent} @@ -100,19 +100,20 @@ export const PinnedFeeds = withAuthRequired( }), ) -const PinnedItem = observer( +const ListItem = observer( ({item, drag}: {item: CustomFeedModel; drag: () => void}) => { const pal = usePalette('default') const rootStore = useStores() const savedFeeds = useMemo(() => rootStore.me.savedFeeds, [rootStore]) + const isPinned = savedFeeds.isPinned(item) return ( <ScaleDecorator> <ShadowDecorator> <Pressable accessibilityRole="button" - onLongPress={drag} - style={styles.itemContainer}> - {isWeb ? ( + onLongPress={isPinned ? drag : undefined} + style={[styles.itemContainer, pal.border]}> + {isPinned && isWeb ? ( <View style={styles.webArrowButtonsContainer}> <TouchableOpacity accessibilityRole="button" @@ -122,7 +123,7 @@ const PinnedItem = observer( <FontAwesomeIcon icon="arrow-up" size={20} - style={[styles.icon, pal.text, styles.webArrowUpButton]} + style={[s.mr10, pal.text, styles.webArrowUpButton]} /> </TouchableOpacity> <TouchableOpacity @@ -133,18 +134,19 @@ const PinnedItem = observer( <FontAwesomeIcon icon="arrow-down" size={20} - style={[styles.icon, pal.text]} + style={[s.mr10, pal.text]} /> </TouchableOpacity> </View> - ) : ( + ) : isPinned ? ( <FontAwesomeIcon icon="bars" size={20} - style={[styles.icon, pal.text]} + color={pal.colors.text} + style={s.ml20} /> - )} - <SavedFeedItem item={item} savedFeeds={savedFeeds} /> + ) : null} + <SavedFeedItem item={item} savedFeeds={savedFeeds} showSaveBtn /> </Pressable> </ShadowDecorator> </ScaleDecorator> @@ -167,12 +169,8 @@ const styles = StyleSheet.create({ flex: 1, flexDirection: 'row', alignItems: 'center', - marginLeft: 18, + borderTopWidth: 1, }, - item: { - borderTopWidth: 0, - }, - icon: {marginRight: 10}, webArrowButtonsContainer: { flexDirection: 'column', justifyContent: 'space-around', diff --git a/src/view/screens/ProfileCustomFeed.tsx b/src/view/screens/ProfileCustomFeed.tsx index 1113ebf01..681798308 100644 --- a/src/view/screens/ProfileCustomFeed.tsx +++ b/src/view/screens/ProfileCustomFeed.tsx @@ -43,10 +43,10 @@ export const ProfileCustomFeed = withAuthRequired( const onToggleSaved = React.useCallback(async () => { try { - if (currentFeed.isSaved) { - await currentFeed.unsave() + if (currentFeed?.isSaved) { + await currentFeed?.unsave() } else { - await currentFeed.save() + await currentFeed?.save() } } catch (err) { Toast.show( @@ -58,10 +58,10 @@ export const ProfileCustomFeed = withAuthRequired( const onToggleLiked = React.useCallback(async () => { try { - if (currentFeed.isLiked) { - await currentFeed.unlike() + if (currentFeed?.isLiked) { + await currentFeed?.unlike() } else { - await currentFeed.like() + await currentFeed?.like() } } catch (err) { Toast.show( @@ -90,10 +90,12 @@ export const ProfileCustomFeed = withAuthRequired( type={currentFeed?.isSaved ? 'default' : 'inverted'} onPress={onToggleSaved} accessibilityLabel={ - currentFeed?.isSaved ? 'Unsave this feed' : 'Save this feed' + currentFeed?.isSaved ? 'Remove from my feeds' : 'Add to my feeds' } accessibilityHint="" - label={currentFeed?.isSaved ? 'Unsave' : 'Save'} + label={ + currentFeed?.isSaved ? 'Remove from My Feeds' : 'Add to My Feeds' + } /> </View> ) |