Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at 06a8a7efc2946247d44adb982e2b2cb367fd7b64 241 lines 6.6 kB view raw
1import {Pressable, View} from 'react-native' 2import {msg} from '@lingui/macro' 3import {useLingui} from '@lingui/react' 4import {useNavigation, useNavigationState} from '@react-navigation/native' 5 6import {getCurrentRoute} from '#/lib/routes/helpers' 7import {type NavigationProp} from '#/lib/routes/types' 8import {logger} from '#/logger' 9import {emitSoftReset} from '#/state/events' 10import { 11 type SavedFeedSourceInfo, 12 usePinnedFeedsInfos, 13} from '#/state/queries/feed' 14import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' 15import {UserAvatar} from '#/view/com/util/UserAvatar' 16import {atoms as a, useTheme, web} from '#/alf' 17import {useInteractionState} from '#/components/hooks/useInteractionState' 18import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 19import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 20import {Link} from '#/components/Link' 21import {Text} from '#/components/Typography' 22 23export function DesktopFeeds() { 24 const t = useTheme() 25 const {_} = useLingui() 26 const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos() 27 const selectedFeed = useSelectedFeed() 28 const setSelectedFeed = useSetSelectedFeed() 29 const navigation = useNavigation<NavigationProp>() 30 const route = useNavigationState(state => { 31 if (!state) { 32 return {name: 'Home'} 33 } 34 return getCurrentRoute(state) 35 }) 36 37 if (isLoading) { 38 return ( 39 <View style={[{gap: 10}]}> 40 {Array(5) 41 .fill(0) 42 .map((_, i) => ( 43 <View 44 key={i} 45 style={[ 46 a.rounded_sm, 47 t.atoms.bg_contrast_25, 48 { 49 height: 16, 50 width: i % 2 === 0 ? '60%' : '80%', 51 }, 52 ]} 53 /> 54 ))} 55 </View> 56 ) 57 } 58 59 if (error || !pinnedFeedInfos) { 60 return null 61 } 62 63 return ( 64 <View 65 style={[ 66 a.flex_1, 67 web({ 68 gap: 2, 69 /* 70 * Small padding prevents overflow prior to actually overflowing the 71 * height of the screen with lots of feeds. 72 */ 73 paddingTop: 2, 74 overflowY: 'auto', 75 }), 76 ]}> 77 {pinnedFeedInfos.map((feedInfo, index) => { 78 const feed = feedInfo.feedDescriptor 79 const current = 80 route.name === 'Home' && 81 (selectedFeed ? feed === selectedFeed : index === 0) 82 83 return ( 84 <FeedItem 85 key={feedInfo.uri} 86 feedInfo={feedInfo} 87 current={current} 88 onPress={() => { 89 logger.metric( 90 'desktopFeeds:feed:click', 91 { 92 feedUri: feedInfo.uri, 93 feedDescriptor: feed, 94 }, 95 {statsig: false}, 96 ) 97 setSelectedFeed(feed) 98 navigation.navigate('Home') 99 if (route.name === 'Home' && feed === selectedFeed) { 100 emitSoftReset() 101 } 102 }} 103 /> 104 ) 105 })} 106 107 <Link 108 to="/feeds" 109 label={_(msg`More feeds`)} 110 style={[ 111 a.flex_row, 112 a.align_center, 113 a.gap_sm, 114 a.self_start, 115 a.rounded_sm, 116 {paddingVertical: 6, paddingHorizontal: 8}, 117 route.name === 'Feeds' && {backgroundColor: t.palette.primary_50}, 118 ]}> 119 {({hovered}) => { 120 const isActive = route.name === 'Feeds' 121 return ( 122 <> 123 <View 124 style={[ 125 a.align_center, 126 a.justify_center, 127 a.rounded_xs, 128 isActive 129 ? {backgroundColor: t.palette.primary_100} 130 : t.atoms.bg_contrast_50, 131 { 132 width: 20, 133 height: 20, 134 }, 135 ]}> 136 <Plus 137 style={{width: 16, height: 16}} 138 fill={ 139 isActive || hovered 140 ? t.atoms.text.color 141 : t.atoms.text_contrast_medium.color 142 } 143 /> 144 </View> 145 <Text 146 style={[ 147 a.text_md, 148 a.leading_snug, 149 isActive 150 ? [t.atoms.text, a.font_semi_bold] 151 : hovered 152 ? t.atoms.text 153 : t.atoms.text_contrast_medium, 154 ]} 155 numberOfLines={1}> 156 {_(msg`More feeds`)} 157 </Text> 158 </> 159 ) 160 }} 161 </Link> 162 </View> 163 ) 164} 165 166function FeedItem({ 167 feedInfo, 168 current, 169 onPress, 170}: { 171 feedInfo: SavedFeedSourceInfo 172 current: boolean 173 onPress: () => void 174}) { 175 const t = useTheme() 176 const {_} = useLingui() 177 const { 178 state: hovered, 179 onIn: onHoverIn, 180 onOut: onHoverOut, 181 } = useInteractionState() 182 const isFollowing = feedInfo.feedDescriptor === 'following' 183 184 return ( 185 <Pressable 186 accessibilityRole="link" 187 accessibilityLabel={feedInfo.displayName} 188 accessibilityHint={_(msg`Opens ${feedInfo.displayName} feed`)} 189 onPress={onPress} 190 onHoverIn={onHoverIn} 191 onHoverOut={onHoverOut} 192 style={[ 193 a.flex_row, 194 a.align_center, 195 a.gap_sm, 196 a.self_start, 197 a.rounded_sm, 198 {paddingVertical: 6, paddingHorizontal: 8}, 199 current && {backgroundColor: t.palette.primary_50}, 200 ]}> 201 {isFollowing ? ( 202 <View 203 style={[ 204 a.align_center, 205 a.justify_center, 206 a.rounded_xs, 207 { 208 width: 20, 209 height: 20, 210 backgroundColor: t.palette.primary_500, 211 }, 212 ]}> 213 <FilterTimeline 214 style={{width: 14, height: 14}} 215 fill={t.palette.white} 216 /> 217 </View> 218 ) : ( 219 <UserAvatar 220 type={feedInfo.type === 'list' ? 'list' : 'algo'} 221 size={20} 222 avatar={feedInfo.avatar} 223 noBorder 224 /> 225 )} 226 <Text 227 style={[ 228 a.text_md, 229 a.leading_snug, 230 current 231 ? [t.atoms.text, a.font_semi_bold] 232 : hovered 233 ? t.atoms.text 234 : t.atoms.text_contrast_medium, 235 ]} 236 numberOfLines={1}> 237 {feedInfo.displayName} 238 </Text> 239 </Pressable> 240 ) 241}