Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replace draglist due to upstream errors (#1795)

* Replace draggable flatlist with simple sort buttons

* Remove react-native-draggable-flatlist dep

* Fix hitslops

* Update lockfile

* Remove bad flex:1

authored by

Paul Frazee and committed by
GitHub
0f4bfcb0 0e8723c3

+133 -172
-1
package.json
··· 128 128 "react-dom": "^18.2.0", 129 129 "react-native": "0.72.5", 130 130 "react-native-appstate-hook": "^1.0.6", 131 - "react-native-draggable-flatlist": "^4.0.1", 132 131 "react-native-drawer-layout": "^3.2.0", 133 132 "react-native-fs": "^2.20.0", 134 133 "react-native-gesture-handler": "^2.12.1",
+3 -7
src/state/models/ui/saved-feeds.ts
··· 95 95 return 96 96 } 97 97 if (direction === 'up' && index !== 0) { 98 - const temp = pinned[index] 99 - pinned[index] = pinned[index - 1] 100 - pinned[index - 1] = temp 98 + ;[pinned[index], pinned[index - 1]] = [pinned[index - 1], pinned[index]] 101 99 } else if (direction === 'down' && index < pinned.length - 1) { 102 - const temp = pinned[index] 103 - pinned[index] = pinned[index + 1] 104 - pinned[index + 1] = temp 100 + ;[pinned[index], pinned[index + 1]] = [pinned[index + 1], pinned[index]] 105 101 } 102 + this._updatePinSortOrder(pinned.concat(this.unpinned.map(f => f.uri))) 106 103 await this.rootStore.preferences.setSavedFeeds( 107 104 this.rootStore.preferences.savedFeeds, 108 105 pinned, 109 106 ) 110 - this._updatePinSortOrder() 111 107 track('CustomFeed:Reorder', { 112 108 name: item.displayName, 113 109 uri: item.uri,
+129 -156
src/view/screens/SavedFeeds.tsx
··· 1 1 import React, {useCallback, useMemo} from 'react' 2 2 import { 3 - RefreshControl, 4 3 StyleSheet, 5 4 View, 6 5 ActivityIndicator, ··· 18 17 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 19 18 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 20 19 import {ViewHeader} from 'view/com/util/ViewHeader' 21 - import {CenteredView} from 'view/com/util/Views' 20 + import {ScrollView, CenteredView} from 'view/com/util/Views' 22 21 import {Text} from 'view/com/util/text/Text' 23 - import {isWeb} from 'platform/detection' 24 22 import {s, colors} from 'lib/styles' 25 - import DraggableFlatList, { 26 - ShadowDecorator, 27 - ScaleDecorator, 28 - } from 'react-native-draggable-flatlist' 29 23 import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' 30 24 import {FeedSourceModel} from 'state/models/content/feed-source' 31 25 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 32 26 import * as Toast from 'view/com/util/Toast' 33 27 import {Haptics} from 'lib/haptics' 34 - import {Link, TextLink} from 'view/com/util/Link' 28 + import {TextLink} from 'view/com/util/Link' 35 29 36 - type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 30 + const HITSLOP_TOP = { 31 + top: 20, 32 + left: 20, 33 + bottom: 5, 34 + right: 20, 35 + } 36 + const HITSLOP_BOTTOM = { 37 + top: 5, 38 + left: 20, 39 + bottom: 20, 40 + right: 20, 41 + } 37 42 43 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 38 44 export const SavedFeeds = withAuthRequired( 39 45 observer(function SavedFeedsImpl({}: Props) { 40 46 const pal = usePalette('default') ··· 55 61 }, [screen, store, savedFeeds]), 56 62 ) 57 63 58 - const renderListEmptyComponent = useCallback(() => { 59 - return ( 60 - <View 61 - style={[ 62 - pal.border, 63 - isMobile && s.flex1, 64 - pal.viewLight, 65 - styles.empty, 66 - ]}> 67 - <Text type="lg" style={[pal.text]}> 68 - You don't have any saved feeds. 69 - </Text> 70 - </View> 71 - ) 72 - }, [pal, isMobile]) 64 + return ( 65 + <CenteredView 66 + style={[ 67 + s.hContentRegion, 68 + pal.border, 69 + isTabletOrDesktop && styles.desktopContainer, 70 + ]}> 71 + <ViewHeader title="Edit My Feeds" showOnDesktop showBorder /> 72 + <ScrollView style={s.flex1}> 73 + <View style={[pal.text, pal.border, styles.title]}> 74 + <Text type="title" style={pal.text}> 75 + Pinned Feeds 76 + </Text> 77 + </View> 78 + {savedFeeds.hasLoaded ? ( 79 + !savedFeeds.pinned.length ? ( 80 + <View 81 + style={[ 82 + pal.border, 83 + isMobile && s.flex1, 84 + pal.viewLight, 85 + styles.empty, 86 + ]}> 87 + <Text type="lg" style={[pal.text]}> 88 + You don't have any pinned feeds. 89 + </Text> 90 + </View> 91 + ) : ( 92 + savedFeeds.pinned.map(feed => ( 93 + <ListItem 94 + key={feed._reactKey} 95 + savedFeeds={savedFeeds} 96 + item={feed} 97 + /> 98 + )) 99 + ) 100 + ) : ( 101 + <ActivityIndicator style={{marginTop: 20}} /> 102 + )} 103 + <View style={[pal.text, pal.border, styles.title]}> 104 + <Text type="title" style={pal.text}> 105 + Saved Feeds 106 + </Text> 107 + </View> 108 + {savedFeeds.hasLoaded ? ( 109 + !savedFeeds.unpinned.length ? ( 110 + <View 111 + style={[ 112 + pal.border, 113 + isMobile && s.flex1, 114 + pal.viewLight, 115 + styles.empty, 116 + ]}> 117 + <Text type="lg" style={[pal.text]}> 118 + You don't have any saved feeds. 119 + </Text> 120 + </View> 121 + ) : ( 122 + savedFeeds.unpinned.map(feed => ( 123 + <ListItem 124 + key={feed._reactKey} 125 + savedFeeds={savedFeeds} 126 + item={feed} 127 + /> 128 + )) 129 + ) 130 + ) : ( 131 + <ActivityIndicator style={{marginTop: 20}} /> 132 + )} 73 133 74 - const renderListFooterComponent = useCallback(() => { 75 - return ( 76 - <> 77 - <View style={[styles.footerLinks, pal.border]}> 78 - <Link style={styles.footerLink} href="/feeds"> 79 - <FontAwesomeIcon 80 - icon="search" 81 - size={18} 82 - color={pal.colors.icon} 83 - /> 84 - <Text type="lg-medium" style={pal.textLight}> 85 - Discover new feeds 86 - </Text> 87 - </Link> 88 - </View> 89 134 <View style={styles.footerText}> 90 135 <Text type="sm" style={pal.textLight}> 91 136 Feeds are custom algorithms that users build with a little coding ··· 99 144 for more information. 100 145 </Text> 101 146 </View> 102 - {savedFeeds.isLoading && <ActivityIndicator />} 103 - </> 104 - ) 105 - }, [pal, savedFeeds.isLoading]) 106 - 107 - const onRefresh = useCallback(() => savedFeeds.refresh(), [savedFeeds]) 108 - 109 - const onDragEnd = useCallback( 110 - async ({data}: {data: FeedSourceModel[]}) => { 111 - try { 112 - await savedFeeds.reorderPinnedFeeds(data) 113 - } catch (e) { 114 - Toast.show('There was an issue contacting the server') 115 - store.log.error('Failed to save pinned feed order', {e}) 116 - } 117 - }, 118 - [savedFeeds, store], 119 - ) 120 - 121 - return ( 122 - <CenteredView 123 - style={[ 124 - s.hContentRegion, 125 - pal.border, 126 - isTabletOrDesktop && styles.desktopContainer, 127 - ]}> 128 - <ViewHeader title="Edit My Feeds" showOnDesktop showBorder /> 129 - <DraggableFlatList 130 - containerStyle={[isTabletOrDesktop ? s.hContentRegion : s.flex1]} 131 - data={savedFeeds.pinned.concat(savedFeeds.unpinned)} 132 - keyExtractor={item => item.uri} 133 - refreshing={savedFeeds.isRefreshing} 134 - refreshControl={ 135 - <RefreshControl 136 - refreshing={savedFeeds.isRefreshing} 137 - onRefresh={onRefresh} 138 - tintColor={pal.colors.text} 139 - titleColor={pal.colors.text} 140 - /> 141 - } 142 - renderItem={({item, drag}) => ( 143 - <ListItem savedFeeds={savedFeeds} item={item} drag={drag} /> 144 - )} 145 - getItemLayout={(data, index) => ({ 146 - length: 77, 147 - offset: 77 * index, 148 - index, 149 - })} 150 - initialNumToRender={10} 151 - ListFooterComponent={renderListFooterComponent} 152 - ListEmptyComponent={renderListEmptyComponent} 153 - extraData={savedFeeds.isLoading} 154 - onDragEnd={onDragEnd} 155 - /> 147 + <View style={{height: 100}} /> 148 + </ScrollView> 156 149 </CenteredView> 157 150 ) 158 151 }), ··· 161 154 const ListItem = observer(function ListItemImpl({ 162 155 savedFeeds, 163 156 item, 164 - drag, 165 157 }: { 166 158 savedFeeds: SavedFeedsModel 167 159 item: FeedSourceModel 168 - drag: () => void 169 160 }) { 170 161 const pal = usePalette('default') 171 162 const store = useStores() ··· 196 187 ) 197 188 198 189 return ( 199 - <ScaleDecorator> 200 - <ShadowDecorator> 201 - <Pressable 202 - accessibilityRole="button" 203 - onLongPress={isPinned ? drag : undefined} 204 - delayLongPress={200} 205 - style={[styles.itemContainer, pal.border]}> 206 - {isPinned && isWeb ? ( 207 - <View style={styles.webArrowButtonsContainer}> 208 - <TouchableOpacity accessibilityRole="button" onPress={onPressUp}> 209 - <FontAwesomeIcon 210 - icon="arrow-up" 211 - size={12} 212 - style={[pal.text, styles.webArrowUpButton]} 213 - /> 214 - </TouchableOpacity> 215 - <TouchableOpacity 216 - accessibilityRole="button" 217 - onPress={onPressDown}> 218 - <FontAwesomeIcon 219 - icon="arrow-down" 220 - size={12} 221 - style={[pal.text]} 222 - /> 223 - </TouchableOpacity> 224 - </View> 225 - ) : isPinned ? ( 226 - <FontAwesomeIcon 227 - icon="bars" 228 - size={20} 229 - color={pal.colors.text} 230 - style={s.ml20} 231 - /> 232 - ) : null} 233 - <FeedSourceCard 234 - key={item.uri} 235 - item={item} 236 - showSaveBtn 237 - style={styles.noBorder} 238 - /> 190 + <Pressable 191 + accessibilityRole="button" 192 + style={[styles.itemContainer, pal.border]}> 193 + {isPinned ? ( 194 + <View style={styles.webArrowButtonsContainer}> 239 195 <TouchableOpacity 240 196 accessibilityRole="button" 241 - hitSlop={10} 242 - onPress={onTogglePinned}> 197 + onPress={onPressUp} 198 + hitSlop={HITSLOP_TOP}> 243 199 <FontAwesomeIcon 244 - icon="thumb-tack" 245 - size={20} 246 - color={isPinned ? colors.blue3 : pal.colors.icon} 200 + icon="arrow-up" 201 + size={12} 202 + style={[pal.text, styles.webArrowUpButton]} 247 203 /> 248 204 </TouchableOpacity> 249 - </Pressable> 250 - </ShadowDecorator> 251 - </ScaleDecorator> 205 + <TouchableOpacity 206 + accessibilityRole="button" 207 + onPress={onPressDown} 208 + hitSlop={HITSLOP_BOTTOM}> 209 + <FontAwesomeIcon icon="arrow-down" size={12} style={[pal.text]} /> 210 + </TouchableOpacity> 211 + </View> 212 + ) : null} 213 + <FeedSourceCard 214 + key={item.uri} 215 + item={item} 216 + showSaveBtn 217 + style={styles.noBorder} 218 + /> 219 + <TouchableOpacity 220 + accessibilityRole="button" 221 + hitSlop={10} 222 + onPress={onTogglePinned}> 223 + <FontAwesomeIcon 224 + icon="thumb-tack" 225 + size={20} 226 + color={isPinned ? colors.blue3 : pal.colors.icon} 227 + /> 228 + </TouchableOpacity> 229 + </Pressable> 252 230 ) 253 231 }) 254 232 ··· 262 240 empty: { 263 241 paddingHorizontal: 20, 264 242 paddingVertical: 20, 265 - borderRadius: 16, 266 - marginHorizontal: 24, 243 + borderRadius: 8, 244 + marginHorizontal: 10, 267 245 marginTop: 10, 268 246 }, 247 + title: { 248 + paddingHorizontal: 14, 249 + paddingTop: 20, 250 + paddingBottom: 10, 251 + borderBottomWidth: 1, 252 + }, 269 253 itemContainer: { 270 - flex: 1, 271 254 flexDirection: 'row', 272 255 alignItems: 'center', 273 256 borderBottomWidth: 1, ··· 288 271 paddingHorizontal: 26, 289 272 paddingTop: 22, 290 273 paddingBottom: 100, 291 - }, 292 - footerLinks: { 293 - borderBottomWidth: 1, 294 - borderTopWidth: 0, 295 - }, 296 - footerLink: { 297 - flexDirection: 'row', 298 - paddingHorizontal: 26, 299 - paddingVertical: 18, 300 - gap: 18, 301 274 }, 302 275 })
+1 -8
yarn.lock
··· 1483 1483 "@babel/plugin-transform-react-jsx-development" "^7.22.5" 1484 1484 "@babel/plugin-transform-react-pure-annotations" "^7.22.5" 1485 1485 1486 - "@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.17.12": 1486 + "@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.16.7": 1487 1487 version "7.22.5" 1488 1488 resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" 1489 1489 integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== ··· 15800 15800 integrity sha512-dbyd+mcy7SUzxEgmt33TRf1FGcNe6swJhXmB0unKkI49F7+pidog9kPtjxMLTAfmKA8gcN2XHQSKltGfGbGCLQ== 15801 15801 dependencies: 15802 15802 dotenv "^16.3.1" 15803 - 15804 - react-native-draggable-flatlist@^4.0.1: 15805 - version "4.0.1" 15806 - resolved "https://registry.yarnpkg.com/react-native-draggable-flatlist/-/react-native-draggable-flatlist-4.0.1.tgz#2f027d387ba4b8f3eb0907340e32cb85e6460df2" 15807 - integrity sha512-ZO1QUTNx64KZfXGXeXcBfql67l38X7kBcJ3rxUVZzPHt5r035GnGzIC0F8rqSXp6zgnwgUYMfB6zQc5PKmPL9Q== 15808 - dependencies: 15809 - "@babel/preset-typescript" "^7.17.12" 15810 15803 15811 15804 react-native-drawer-layout@^3.2.0: 15812 15805 version "3.2.1"