mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
fork

Configure Feed

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

at verify-code 349 lines 9.9 kB view raw
1import React from 'react' 2import { 3 Linking, 4 Pressable, 5 StyleProp, 6 StyleSheet, 7 View, 8 ViewStyle, 9} from 'react-native' 10import {AtUri} from '@atproto/api' 11import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12import {msg, Plural, Trans} from '@lingui/macro' 13import {useLingui} from '@lingui/react' 14 15import {logger} from '#/logger' 16import {shouldClickOpenNewTab} from '#/platform/urls' 17import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed' 18import { 19 useAddSavedFeedsMutation, 20 usePreferencesQuery, 21 UsePreferencesQueryResponse, 22 useRemoveFeedMutation, 23} from '#/state/queries/preferences' 24import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' 25import {usePalette} from 'lib/hooks/usePalette' 26import {sanitizeHandle} from 'lib/strings/handles' 27import {s} from 'lib/styles' 28import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 29import * as Toast from 'view/com/util/Toast' 30import {useTheme} from '#/alf' 31import {atoms as a} from '#/alf' 32import * as Prompt from '#/components/Prompt' 33import {RichText} from '#/components/RichText' 34import {Text} from '../util/text/Text' 35import {UserAvatar} from '../util/UserAvatar' 36 37export function FeedSourceCard({ 38 feedUri, 39 style, 40 showSaveBtn = false, 41 showDescription = false, 42 showLikes = false, 43 pinOnSave = false, 44 showMinimalPlaceholder, 45 hideTopBorder, 46}: { 47 feedUri: string 48 style?: StyleProp<ViewStyle> 49 showSaveBtn?: boolean 50 showDescription?: boolean 51 showLikes?: boolean 52 pinOnSave?: boolean 53 showMinimalPlaceholder?: boolean 54 hideTopBorder?: boolean 55}) { 56 const {data: preferences} = usePreferencesQuery() 57 const {data: feed} = useFeedSourceInfoQuery({uri: feedUri}) 58 59 return ( 60 <FeedSourceCardLoaded 61 feedUri={feedUri} 62 feed={feed} 63 preferences={preferences} 64 style={style} 65 showSaveBtn={showSaveBtn} 66 showDescription={showDescription} 67 showLikes={showLikes} 68 pinOnSave={pinOnSave} 69 showMinimalPlaceholder={showMinimalPlaceholder} 70 hideTopBorder={hideTopBorder} 71 /> 72 ) 73} 74 75export function FeedSourceCardLoaded({ 76 feedUri, 77 feed, 78 preferences, 79 style, 80 showSaveBtn = false, 81 showDescription = false, 82 showLikes = false, 83 pinOnSave = false, 84 showMinimalPlaceholder, 85 hideTopBorder, 86}: { 87 feedUri: string 88 feed?: FeedSourceInfo 89 preferences?: UsePreferencesQueryResponse 90 style?: StyleProp<ViewStyle> 91 showSaveBtn?: boolean 92 showDescription?: boolean 93 showLikes?: boolean 94 pinOnSave?: boolean 95 showMinimalPlaceholder?: boolean 96 hideTopBorder?: boolean 97}) { 98 const t = useTheme() 99 const pal = usePalette('default') 100 const {_} = useLingui() 101 const removePromptControl = Prompt.usePromptControl() 102 const navigation = useNavigationDeduped() 103 104 const {isPending: isAddSavedFeedPending, mutateAsync: addSavedFeeds} = 105 useAddSavedFeedsMutation() 106 const {isPending: isRemovePending, mutateAsync: removeFeed} = 107 useRemoveFeedMutation() 108 109 const savedFeedConfig = preferences?.savedFeeds?.find( 110 f => f.value === feedUri, 111 ) 112 const isSaved = Boolean(savedFeedConfig) 113 114 const onSave = React.useCallback(async () => { 115 if (!feed || isSaved) return 116 117 try { 118 await addSavedFeeds([ 119 { 120 type: 'feed', 121 value: feed.uri, 122 pinned: pinOnSave, 123 }, 124 ]) 125 Toast.show(_(msg`Added to my feeds`)) 126 } catch (e) { 127 Toast.show(_(msg`There was an issue contacting your server`), 'xmark') 128 logger.error('Failed to save feed', {message: e}) 129 } 130 }, [_, feed, pinOnSave, addSavedFeeds, isSaved]) 131 132 const onUnsave = React.useCallback(async () => { 133 if (!savedFeedConfig) return 134 135 try { 136 await removeFeed(savedFeedConfig) 137 // await item.unsave() 138 Toast.show(_(msg`Removed from my feeds`)) 139 } catch (e) { 140 Toast.show(_(msg`There was an issue contacting your server`), 'xmark') 141 logger.error('Failed to unsave feed', {message: e}) 142 } 143 }, [_, removeFeed, savedFeedConfig]) 144 145 const onToggleSaved = React.useCallback(async () => { 146 if (isSaved) { 147 removePromptControl.open() 148 } else { 149 await onSave() 150 } 151 }, [isSaved, removePromptControl, onSave]) 152 153 /* 154 * LOAD STATE 155 * 156 * This state also captures the scenario where a feed can't load for whatever 157 * reason. 158 */ 159 if (!feed || !preferences) 160 return ( 161 <View 162 style={[ 163 pal.border, 164 { 165 borderTopWidth: showMinimalPlaceholder || hideTopBorder ? 0 : 1, 166 flexDirection: 'row', 167 alignItems: 'center', 168 flex: 1, 169 paddingRight: 18, 170 }, 171 ]}> 172 {showMinimalPlaceholder ? ( 173 <FeedLoadingPlaceholder 174 style={{flex: 1}} 175 showTopBorder={false} 176 showLowerPlaceholder={false} 177 /> 178 ) : ( 179 <FeedLoadingPlaceholder style={{flex: 1}} showTopBorder={false} /> 180 )} 181 182 {showSaveBtn && ( 183 <Pressable 184 testID={`feed-${feedUri}-toggleSave`} 185 disabled={isRemovePending} 186 accessibilityRole="button" 187 accessibilityLabel={_(msg`Remove from my feeds`)} 188 accessibilityHint="" 189 onPress={onUnsave} 190 hitSlop={15} 191 style={styles.btn}> 192 <FontAwesomeIcon 193 icon={['far', 'trash-can']} 194 size={19} 195 color={pal.colors.icon} 196 /> 197 </Pressable> 198 )} 199 </View> 200 ) 201 202 return ( 203 <> 204 <Pressable 205 testID={`feed-${feed.displayName}`} 206 accessibilityRole="button" 207 style={[ 208 styles.container, 209 pal.border, 210 style, 211 {borderTopWidth: hideTopBorder ? 0 : StyleSheet.hairlineWidth}, 212 ]} 213 onPress={e => { 214 const shouldOpenInNewTab = shouldClickOpenNewTab(e) 215 if (feed.type === 'feed') { 216 if (shouldOpenInNewTab) { 217 Linking.openURL( 218 `/profile/${feed.creatorDid}/feed/${new AtUri(feed.uri).rkey}`, 219 ) 220 } else { 221 navigation.push('ProfileFeed', { 222 name: feed.creatorDid, 223 rkey: new AtUri(feed.uri).rkey, 224 }) 225 } 226 } else if (feed.type === 'list') { 227 if (shouldOpenInNewTab) { 228 Linking.openURL( 229 `/profile/${feed.creatorDid}/lists/${new AtUri(feed.uri).rkey}`, 230 ) 231 } else { 232 navigation.push('ProfileList', { 233 name: feed.creatorDid, 234 rkey: new AtUri(feed.uri).rkey, 235 }) 236 } 237 } 238 }} 239 key={feed.uri}> 240 <View style={[styles.headerContainer, a.align_center]}> 241 <View style={[s.mr10]}> 242 <UserAvatar type="algo" size={36} avatar={feed.avatar} /> 243 </View> 244 <View style={[styles.headerTextContainer]}> 245 <Text style={[pal.text, s.bold]} numberOfLines={1}> 246 {feed.displayName} 247 </Text> 248 <Text style={[pal.textLight]} numberOfLines={1}> 249 {feed.type === 'feed' ? ( 250 <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 251 ) : ( 252 <Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 253 )} 254 </Text> 255 </View> 256 257 {showSaveBtn && ( 258 <View style={{alignSelf: 'center'}}> 259 <Pressable 260 testID={`feed-${feed.displayName}-toggleSave`} 261 disabled={isAddSavedFeedPending || isRemovePending} 262 accessibilityRole="button" 263 accessibilityLabel={ 264 isSaved 265 ? _(msg`Remove from my feeds`) 266 : _(msg`Add to my feeds`) 267 } 268 accessibilityHint="" 269 onPress={onToggleSaved} 270 hitSlop={15} 271 style={styles.btn}> 272 {isSaved ? ( 273 <FontAwesomeIcon 274 icon={['far', 'trash-can']} 275 size={19} 276 color={pal.colors.icon} 277 /> 278 ) : ( 279 <FontAwesomeIcon 280 icon="plus" 281 size={18} 282 color={pal.colors.link} 283 /> 284 )} 285 </Pressable> 286 </View> 287 )} 288 </View> 289 290 {showDescription && feed.description ? ( 291 <RichText 292 style={[t.atoms.text_contrast_high, styles.description]} 293 value={feed.description} 294 numberOfLines={3} 295 /> 296 ) : null} 297 298 {showLikes && feed.type === 'feed' ? ( 299 <Text type="sm-medium" style={[pal.text, pal.textLight]}> 300 <Plural 301 value={feed.likeCount || 0} 302 one="Liked by # user" 303 other="Liked by # users" 304 /> 305 </Text> 306 ) : null} 307 </Pressable> 308 309 <Prompt.Basic 310 control={removePromptControl} 311 title={_(msg`Remove from my feeds?`)} 312 description={_( 313 msg`Are you sure you want to remove ${feed.displayName} from your feeds?`, 314 )} 315 onConfirm={onUnsave} 316 confirmButtonCta={_(msg`Remove`)} 317 confirmButtonColor="negative" 318 /> 319 </> 320 ) 321} 322 323const styles = StyleSheet.create({ 324 container: { 325 paddingHorizontal: 18, 326 paddingVertical: 20, 327 flexDirection: 'column', 328 flex: 1, 329 gap: 14, 330 }, 331 border: { 332 borderTopWidth: StyleSheet.hairlineWidth, 333 }, 334 headerContainer: { 335 flexDirection: 'row', 336 }, 337 headerTextContainer: { 338 flexDirection: 'column', 339 columnGap: 4, 340 flex: 1, 341 }, 342 description: { 343 flex: 1, 344 flexWrap: 'wrap', 345 }, 346 btn: { 347 paddingVertical: 6, 348 }, 349})