Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Fixes and improvements to the Profile Preview modal (#992)

* Fix: use more reliable navigation method

* Fix: show lightbox over the active modal

* Fix: close the profile preview on navigation

* Factor out UserPreviewLink and add preview behavior to notifications

* Fix postmeta overflow on web

* Fix lint

authored by

Paul Frazee and committed by
GitHub
237e957d d8aded7b

+101 -59
+18
src/state/models/ui/shell.ts
··· 279 279 return false 280 280 } 281 281 282 + /** 283 + * used to clear out any modals, eg for a navigation 284 + */ 285 + closeAllActiveElements() { 286 + if (this.isLightboxActive) { 287 + this.closeLightbox() 288 + } 289 + while (this.isModalActive) { 290 + this.closeModal() 291 + } 292 + if (this.isComposerActive) { 293 + this.closeComposer() 294 + } 295 + if (this.isDrawerOpen) { 296 + this.closeDrawer() 297 + } 298 + } 299 + 282 300 openDrawer() { 283 301 this.isDrawerOpen = true 284 302 }
+18 -19
src/view/com/notifications/FeedItem.tsx
··· 22 22 import {pluralize} from 'lib/strings/helpers' 23 23 import {HeartIconSolid} from 'lib/icons' 24 24 import {Text} from '../util/text/Text' 25 - import {UserAvatar} from '../util/UserAvatar' 25 + import {UserAvatar, PreviewableUserAvatar} from '../util/UserAvatar' 26 + import {UserPreviewLink} from '../util/UserPreviewLink' 26 27 import {ImageHorzList} from '../util/images/ImageHorzList' 27 28 import {Post} from '../post/Post' 28 29 import {Link, TextLink} from '../util/Link' ··· 42 43 43 44 interface Author { 44 45 href: string 46 + did: string 45 47 handle: string 46 48 displayName?: string 47 49 avatar?: string ··· 91 93 return [ 92 94 { 93 95 href: `/profile/${item.author.handle}`, 96 + did: item.author.did, 94 97 handle: item.author.handle, 95 98 displayName: item.author.displayName, 96 99 avatar: item.author.avatar, ··· 102 105 ...(item.additional?.map(({author}) => { 103 106 return { 104 107 href: `/profile/${author.handle}`, 108 + did: author.did, 105 109 handle: author.handle, 106 110 displayName: author.displayName, 107 111 avatar: author.avatar, ··· 282 286 if (authors.length === 1) { 283 287 return ( 284 288 <View style={styles.avis}> 285 - <Link 286 - style={s.mr5} 287 - href={authors[0].href} 288 - title={`@${authors[0].handle}`} 289 - asAnchor> 290 - <UserAvatar 291 - size={35} 292 - avatar={authors[0].avatar} 293 - moderation={authors[0].moderation.avatar} 294 - /> 295 - </Link> 289 + <PreviewableUserAvatar 290 + size={35} 291 + did={authors[0].did} 292 + handle={authors[0].handle} 293 + avatar={authors[0].avatar} 294 + moderation={authors[0].moderation.avatar} 295 + /> 296 296 </View> 297 297 ) 298 298 } ··· 356 356 visible ? s.mb10 : undefined, 357 357 ]}> 358 358 {authors.map(author => ( 359 - <Link 360 - key={author.href} 361 - href={author.href} 362 - title={sanitizeDisplayName(author.displayName || author.handle)} 363 - style={styles.expandedAuthor} 364 - asAnchor> 359 + <UserPreviewLink 360 + key={author.did} 361 + did={author.did} 362 + handle={author.handle} 363 + style={styles.expandedAuthor}> 365 364 <View style={styles.expandedAuthorAvi}> 366 365 <UserAvatar 367 366 size={35} ··· 382 381 </Text> 383 382 </Text> 384 383 </View> 385 - </Link> 384 + </UserPreviewLink> 386 385 ))} 387 386 </Animated.View> 388 387 )
+8 -10
src/view/com/post/Post.tsx
··· 25 25 import {Text} from '../util/text/Text' 26 26 import {RichText} from '../util/text/RichText' 27 27 import * as Toast from '../util/Toast' 28 - import {UserAvatar} from '../util/UserAvatar' 28 + import {PreviewableUserAvatar} from '../util/UserAvatar' 29 29 import {useStores} from 'state/index' 30 30 import {s, colors} from 'lib/styles' 31 31 import {usePalette} from 'lib/hooks/usePalette' ··· 127 127 const itemUrip = new AtUri(item.post.uri) 128 128 const itemHref = `/profile/${item.post.author.handle}/post/${itemUrip.rkey}` 129 129 const itemTitle = `Post by ${item.post.author.handle}` 130 - const authorHref = `/profile/${item.post.author.handle}` 131 - const authorTitle = item.post.author.handle 132 130 let replyAuthorDid = '' 133 131 if (record.reply) { 134 132 const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri) ··· 214 212 {showReplyLine && <View style={styles.replyLine} />} 215 213 <View style={styles.layout}> 216 214 <View style={styles.layoutAvi}> 217 - <Link href={authorHref} title={authorTitle} asAnchor> 218 - <UserAvatar 219 - size={52} 220 - avatar={item.post.author.avatar} 221 - moderation={item.moderation.avatar} 222 - /> 223 - </Link> 215 + <PreviewableUserAvatar 216 + size={52} 217 + did={item.post.author.did} 218 + handle={item.post.author.handle} 219 + avatar={item.post.author.avatar} 220 + moderation={item.moderation.avatar} 221 + /> 224 222 </View> 225 223 <View style={styles.layoutContent}> 226 224 <PostMeta
+7 -4
src/view/com/profile/ProfileHeader.tsx
··· 33 33 import {FollowState} from 'state/models/cache/my-follows' 34 34 import {shareUrl} from 'lib/sharing' 35 35 import {formatCount} from '../util/numeric/format' 36 + import {navigate} from '../../../Navigation' 36 37 37 38 const BACK_HITSLOP = {left: 30, top: 30, right: 30, bottom: 30} 38 39 ··· 143 144 144 145 const onPressFollowers = React.useCallback(() => { 145 146 track('ProfileHeader:FollowersButtonClicked') 146 - navigation.push('ProfileFollowers', {name: view.handle}) 147 - }, [track, navigation, view]) 147 + navigate('ProfileFollowers', {name: view.handle}) 148 + store.shell.closeAllActiveElements() // for when used in the profile preview modal 149 + }, [track, view, store.shell]) 148 150 149 151 const onPressFollows = React.useCallback(() => { 150 152 track('ProfileHeader:FollowsButtonClicked') 151 - navigation.push('ProfileFollows', {name: view.handle}) 152 - }, [track, navigation, view]) 153 + navigate('ProfileFollows', {name: view.handle}) 154 + store.shell.closeAllActiveElements() // for when used in the profile preview modal 155 + }, [track, view, store.shell]) 153 156 154 157 const onPressShare = React.useCallback(() => { 155 158 track('ProfileHeader:ShareButtonClicked')
+2 -2
src/view/com/util/PostMeta.tsx
··· 7 7 import {UserAvatar} from './UserAvatar' 8 8 import {observer} from 'mobx-react-lite' 9 9 import {sanitizeDisplayName} from 'lib/strings/display-names' 10 - import {isAndroid, isIOS} from 'platform/detection' 10 + import {isAndroid} from 'platform/detection' 11 11 12 12 interface PostMetaOpts { 13 13 authorAvatar?: string ··· 88 88 }, 89 89 maxWidth: { 90 90 flex: isAndroid ? 1 : undefined, 91 - maxWidth: isIOS ? '80%' : undefined, 91 + maxWidth: !isAndroid ? '80%' : undefined, 92 92 }, 93 93 })
+4 -23
src/view/com/util/UserAvatar.tsx
··· 1 1 import React, {useMemo} from 'react' 2 - import {Pressable, StyleSheet, View} from 'react-native' 2 + import {StyleSheet, View} from 'react-native' 3 3 import Svg, {Circle, Rect, Path} from 'react-native-svg' 4 4 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 5 5 import {IconProp} from '@fortawesome/fontawesome-svg-core' ··· 12 12 import {useStores} from 'state/index' 13 13 import {colors} from 'lib/styles' 14 14 import {DropdownButton} from './forms/DropdownButton' 15 - import {Link} from './Link' 16 15 import {usePalette} from 'lib/hooks/usePalette' 17 16 import {isWeb, isAndroid} from 'platform/detection' 18 17 import {Image as RNImage} from 'react-native-image-crop-picker' 19 18 import {AvatarModeration} from 'lib/labeling/types' 20 - import {isDesktopWeb} from 'platform/detection' 19 + import {UserPreviewLink} from './UserPreviewLink' 21 20 22 21 type Type = 'user' | 'algo' | 'list' 23 22 ··· 257 256 } 258 257 259 258 export function PreviewableUserAvatar(props: PreviewableUserAvatarProps) { 260 - const store = useStores() 261 - 262 - if (isDesktopWeb) { 263 - return ( 264 - <Link href={`/profile/${props.handle}`} title={props.handle} asAnchor> 265 - <UserAvatar {...props} /> 266 - </Link> 267 - ) 268 - } 269 259 return ( 270 - <Pressable 271 - onPress={() => 272 - store.shell.openModal({ 273 - name: 'profile-preview', 274 - did: props.did, 275 - }) 276 - } 277 - accessibilityRole="button" 278 - accessibilityLabel={props.handle} 279 - accessibilityHint=""> 260 + <UserPreviewLink did={props.did} handle={props.handle}> 280 261 <UserAvatar {...props} /> 281 - </Pressable> 262 + </UserPreviewLink> 282 263 ) 283 264 } 284 265
+43
src/view/com/util/UserPreviewLink.tsx
··· 1 + import React from 'react' 2 + import {Pressable, StyleProp, ViewStyle} from 'react-native' 3 + import {useStores} from 'state/index' 4 + import {Link} from './Link' 5 + import {isDesktopWeb} from 'platform/detection' 6 + 7 + interface UserPreviewLinkProps { 8 + did: string 9 + handle: string 10 + style?: StyleProp<ViewStyle> 11 + } 12 + export function UserPreviewLink( 13 + props: React.PropsWithChildren<UserPreviewLinkProps>, 14 + ) { 15 + const store = useStores() 16 + 17 + if (isDesktopWeb) { 18 + return ( 19 + <Link 20 + href={`/profile/${props.handle}`} 21 + title={props.handle} 22 + asAnchor 23 + style={props.style}> 24 + {props.children} 25 + </Link> 26 + ) 27 + } 28 + return ( 29 + <Pressable 30 + onPress={() => 31 + store.shell.openModal({ 32 + name: 'profile-preview', 33 + did: props.did, 34 + }) 35 + } 36 + accessibilityRole="button" 37 + accessibilityLabel={props.handle} 38 + accessibilityHint="" 39 + style={props.style}> 40 + {props.children} 41 + </Pressable> 42 + ) 43 + }
+1 -1
src/view/shell/index.tsx
··· 61 61 </Drawer> 62 62 </ErrorBoundary> 63 63 </View> 64 - <Lightbox /> 65 64 <Composer 66 65 active={store.shell.isComposerActive} 67 66 onClose={() => store.shell.closeComposer()} ··· 71 70 quote={store.shell.composerOpts?.quote} 72 71 /> 73 72 <ModalsContainer /> 73 + <Lightbox /> 74 74 </> 75 75 ) 76 76 })