PWI: Profile (#1982)
* PWI: Profile * Show replies conditionally * Dismiss modals on auth action
This commit is contained in:
parent
edf3114e47
commit
4272d291a9
3 changed files with 129 additions and 105 deletions
|
@ -9,6 +9,7 @@ import {PUBLIC_BSKY_AGENT} from '#/state/queries'
|
||||||
import {IS_PROD} from '#/lib/constants'
|
import {IS_PROD} from '#/lib/constants'
|
||||||
import {emitSessionLoaded, emitSessionDropped} from '../events'
|
import {emitSessionLoaded, emitSessionDropped} from '../events'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
|
|
||||||
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
|
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
|
||||||
|
|
||||||
|
@ -520,15 +521,17 @@ export function useSessionApi() {
|
||||||
export function useRequireAuth() {
|
export function useRequireAuth() {
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
const closeAll = useCloseAllActiveElements()
|
||||||
|
|
||||||
return React.useCallback(
|
return React.useCallback(
|
||||||
(fn: () => void) => {
|
(fn: () => void) => {
|
||||||
if (hasSession) {
|
if (hasSession) {
|
||||||
fn()
|
fn()
|
||||||
} else {
|
} else {
|
||||||
|
closeAll()
|
||||||
setShowLoggedOut(true)
|
setShowLoggedOut(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[hasSession, setShowLoggedOut],
|
[hasSession, setShowLoggedOut, closeAll],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ import {s, colors} from 'lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {Shadow} from '#/state/cache/types'
|
import {Shadow} from '#/state/cache/types'
|
||||||
|
import {useRequireAuth} from '#/state/session'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null
|
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null
|
||||||
|
@ -113,7 +114,8 @@ let ProfileHeaderLoaded = ({
|
||||||
}: LoadedProps): React.ReactNode => {
|
}: LoadedProps): React.ReactNode => {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const palInverted = usePalette('inverted')
|
const palInverted = usePalette('inverted')
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount, hasSession} = useSession()
|
||||||
|
const requireAuth = useRequireAuth()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {openLightbox} = useLightboxControls()
|
const {openLightbox} = useLightboxControls()
|
||||||
|
@ -150,38 +152,42 @@ let ProfileHeaderLoaded = ({
|
||||||
}
|
}
|
||||||
}, [openLightbox, profile, moderation])
|
}, [openLightbox, profile, moderation])
|
||||||
|
|
||||||
const onPressFollow = async () => {
|
const onPressFollow = () => {
|
||||||
try {
|
requireAuth(async () => {
|
||||||
track('ProfileHeader:FollowButtonClicked')
|
try {
|
||||||
await queueFollow()
|
track('ProfileHeader:FollowButtonClicked')
|
||||||
Toast.show(
|
await queueFollow()
|
||||||
`Following ${sanitizeDisplayName(
|
Toast.show(
|
||||||
profile.displayName || profile.handle,
|
`Following ${sanitizeDisplayName(
|
||||||
)}`,
|
profile.displayName || profile.handle,
|
||||||
)
|
)}`,
|
||||||
} catch (e: any) {
|
)
|
||||||
if (e?.name !== 'AbortError') {
|
} catch (e: any) {
|
||||||
logger.error('Failed to follow', {error: String(e)})
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(`There was an issue! ${e.toString()}`)
|
logger.error('Failed to follow', {error: String(e)})
|
||||||
|
Toast.show(`There was an issue! ${e.toString()}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPressUnfollow = async () => {
|
const onPressUnfollow = () => {
|
||||||
try {
|
requireAuth(async () => {
|
||||||
track('ProfileHeader:UnfollowButtonClicked')
|
try {
|
||||||
await queueUnfollow()
|
track('ProfileHeader:UnfollowButtonClicked')
|
||||||
Toast.show(
|
await queueUnfollow()
|
||||||
`No longer following ${sanitizeDisplayName(
|
Toast.show(
|
||||||
profile.displayName || profile.handle,
|
`No longer following ${sanitizeDisplayName(
|
||||||
)}`,
|
profile.displayName || profile.handle,
|
||||||
)
|
)}`,
|
||||||
} catch (e: any) {
|
)
|
||||||
if (e?.name !== 'AbortError') {
|
} catch (e: any) {
|
||||||
logger.error('Failed to unfollow', {error: String(e)})
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(`There was an issue! ${e.toString()}`)
|
logger.error('Failed to unfollow', {error: String(e)})
|
||||||
|
Toast.show(`There was an issue! ${e.toString()}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPressEditProfile = React.useCallback(() => {
|
const onPressEditProfile = React.useCallback(() => {
|
||||||
|
@ -303,72 +309,75 @@ let ProfileHeaderLoaded = ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
items.push({label: 'separator'})
|
if (hasSession) {
|
||||||
items.push({
|
items.push({label: 'separator'})
|
||||||
testID: 'profileHeaderDropdownListAddRemoveBtn',
|
|
||||||
label: _(msg`Add to Lists`),
|
|
||||||
onPress: onPressAddRemoveLists,
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'list.bullet',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_add',
|
|
||||||
web: 'list',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!isMe) {
|
|
||||||
if (!profile.viewer?.blocking) {
|
|
||||||
items.push({
|
|
||||||
testID: 'profileHeaderDropdownMuteBtn',
|
|
||||||
label: profile.viewer?.muted
|
|
||||||
? _(msg`Unmute Account`)
|
|
||||||
: _(msg`Mute Account`),
|
|
||||||
onPress: profile.viewer?.muted
|
|
||||||
? onPressUnmuteAccount
|
|
||||||
: onPressMuteAccount,
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'speaker.slash',
|
|
||||||
},
|
|
||||||
android: 'ic_lock_silent_mode',
|
|
||||||
web: 'comment-slash',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!profile.viewer?.blockingByList) {
|
|
||||||
items.push({
|
|
||||||
testID: 'profileHeaderDropdownBlockBtn',
|
|
||||||
label: profile.viewer?.blocking
|
|
||||||
? _(msg`Unblock Account`)
|
|
||||||
: _(msg`Block Account`),
|
|
||||||
onPress: profile.viewer?.blocking
|
|
||||||
? onPressUnblockAccount
|
|
||||||
: onPressBlockAccount,
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'person.fill.xmark',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_close_clear_cancel',
|
|
||||||
web: 'user-slash',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
items.push({
|
items.push({
|
||||||
testID: 'profileHeaderDropdownReportBtn',
|
testID: 'profileHeaderDropdownListAddRemoveBtn',
|
||||||
label: _(msg`Report Account`),
|
label: _(msg`Add to Lists`),
|
||||||
onPress: onPressReportAccount,
|
onPress: onPressAddRemoveLists,
|
||||||
icon: {
|
icon: {
|
||||||
ios: {
|
ios: {
|
||||||
name: 'exclamationmark.triangle',
|
name: 'list.bullet',
|
||||||
},
|
},
|
||||||
android: 'ic_menu_report_image',
|
android: 'ic_menu_add',
|
||||||
web: 'circle-exclamation',
|
web: 'list',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if (!isMe) {
|
||||||
|
if (!profile.viewer?.blocking) {
|
||||||
|
items.push({
|
||||||
|
testID: 'profileHeaderDropdownMuteBtn',
|
||||||
|
label: profile.viewer?.muted
|
||||||
|
? _(msg`Unmute Account`)
|
||||||
|
: _(msg`Mute Account`),
|
||||||
|
onPress: profile.viewer?.muted
|
||||||
|
? onPressUnmuteAccount
|
||||||
|
: onPressMuteAccount,
|
||||||
|
icon: {
|
||||||
|
ios: {
|
||||||
|
name: 'speaker.slash',
|
||||||
|
},
|
||||||
|
android: 'ic_lock_silent_mode',
|
||||||
|
web: 'comment-slash',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!profile.viewer?.blockingByList) {
|
||||||
|
items.push({
|
||||||
|
testID: 'profileHeaderDropdownBlockBtn',
|
||||||
|
label: profile.viewer?.blocking
|
||||||
|
? _(msg`Unblock Account`)
|
||||||
|
: _(msg`Block Account`),
|
||||||
|
onPress: profile.viewer?.blocking
|
||||||
|
? onPressUnblockAccount
|
||||||
|
: onPressBlockAccount,
|
||||||
|
icon: {
|
||||||
|
ios: {
|
||||||
|
name: 'person.fill.xmark',
|
||||||
|
},
|
||||||
|
android: 'ic_menu_close_clear_cancel',
|
||||||
|
web: 'user-slash',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
testID: 'profileHeaderDropdownReportBtn',
|
||||||
|
label: _(msg`Report Account`),
|
||||||
|
onPress: onPressReportAccount,
|
||||||
|
icon: {
|
||||||
|
ios: {
|
||||||
|
name: 'exclamationmark.triangle',
|
||||||
|
},
|
||||||
|
android: 'ic_menu_report_image',
|
||||||
|
web: 'circle-exclamation',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}, [
|
}, [
|
||||||
isMe,
|
isMe,
|
||||||
|
hasSession,
|
||||||
profile.viewer?.muted,
|
profile.viewer?.muted,
|
||||||
profile.viewer?.blocking,
|
profile.viewer?.blocking,
|
||||||
profile.viewer?.blockingByList,
|
profile.viewer?.blockingByList,
|
||||||
|
@ -421,7 +430,7 @@ let ProfileHeaderLoaded = ({
|
||||||
)
|
)
|
||||||
) : !profile.viewer?.blockedBy ? (
|
) : !profile.viewer?.blockedBy ? (
|
||||||
<>
|
<>
|
||||||
{!isProfilePreview && (
|
{!isProfilePreview && hasSession && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="suggestedFollowsBtn"
|
testID="suggestedFollowsBtn"
|
||||||
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
|
onPress={() => setShowSuggestedFollows(!showSuggestedFollows)}
|
||||||
|
|
|
@ -155,23 +155,27 @@ function ProfileScreenLoaded({
|
||||||
)
|
)
|
||||||
|
|
||||||
const isMe = profile.did === currentAccount?.did
|
const isMe = profile.did === currentAccount?.did
|
||||||
|
const showRepliesTab = hasSession
|
||||||
const showLikesTab = isMe
|
const showLikesTab = isMe
|
||||||
const showFeedsTab = isMe || extraInfoQuery.data?.hasFeedgens
|
const showFeedsTab = hasSession && (isMe || extraInfoQuery.data?.hasFeedgens)
|
||||||
const showListsTab = isMe || extraInfoQuery.data?.hasLists
|
const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists)
|
||||||
const sectionTitles = useMemo<string[]>(() => {
|
const sectionTitles = useMemo<string[]>(() => {
|
||||||
return [
|
return [
|
||||||
'Posts',
|
'Posts',
|
||||||
'Posts & Replies',
|
showRepliesTab ? 'Posts & Replies' : undefined,
|
||||||
'Media',
|
'Media',
|
||||||
showLikesTab ? 'Likes' : undefined,
|
showLikesTab ? 'Likes' : undefined,
|
||||||
showFeedsTab ? 'Feeds' : undefined,
|
showFeedsTab ? 'Feeds' : undefined,
|
||||||
showListsTab ? 'Lists' : undefined,
|
showListsTab ? 'Lists' : undefined,
|
||||||
].filter(Boolean) as string[]
|
].filter(Boolean) as string[]
|
||||||
}, [showLikesTab, showFeedsTab, showListsTab])
|
}, [showRepliesTab, showLikesTab, showFeedsTab, showListsTab])
|
||||||
|
|
||||||
let nextIndex = 0
|
let nextIndex = 0
|
||||||
const postsIndex = nextIndex++
|
const postsIndex = nextIndex++
|
||||||
const repliesIndex = nextIndex++
|
let repliesIndex: number | null = null
|
||||||
|
if (showRepliesTab) {
|
||||||
|
repliesIndex = nextIndex++
|
||||||
|
}
|
||||||
const mediaIndex = nextIndex++
|
const mediaIndex = nextIndex++
|
||||||
let likesIndex: number | null = null
|
let likesIndex: number | null = null
|
||||||
if (showLikesTab) {
|
if (showLikesTab) {
|
||||||
|
@ -282,19 +286,27 @@ function ProfileScreenLoaded({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
{showRepliesTab
|
||||||
<FeedSection
|
? ({
|
||||||
ref={repliesSectionRef}
|
onScroll,
|
||||||
feed={`author|${profile.did}|posts_with_replies`}
|
headerHeight,
|
||||||
onScroll={onScroll}
|
isFocused,
|
||||||
headerHeight={headerHeight}
|
isScrolledDown,
|
||||||
isFocused={isFocused}
|
scrollElRef,
|
||||||
isScrolledDown={isScrolledDown}
|
}) => (
|
||||||
scrollElRef={
|
<FeedSection
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
ref={repliesSectionRef}
|
||||||
}
|
feed={`author|${profile.did}|posts_with_replies`}
|
||||||
/>
|
onScroll={onScroll}
|
||||||
)}
|
headerHeight={headerHeight}
|
||||||
|
isFocused={isFocused}
|
||||||
|
isScrolledDown={isScrolledDown}
|
||||||
|
scrollElRef={
|
||||||
|
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={mediaSectionRef}
|
ref={mediaSectionRef}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue