import React, {useMemo} from 'react' import {ActivityIndicator, StyleSheet, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewSelectorHandle} from '../com/util/ViewSelector' import {CenteredView} from '../com/util/Views' import {ScreenHider} from 'view/com/util/moderation/ScreenHider' import {Feed} from 'view/com/posts/Feed' import {ProfileLists} from '../com/lists/ProfileLists' import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens' import {useStores} from 'state/index' import {ProfileHeader} from '../com/profile/ProfileHeader' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {ErrorScreen} from '../com/util/error/ErrorScreen' import {EmptyState} from '../com/util/EmptyState' import {FAB} from '../com/util/fab/FAB' import {s, colors} from 'lib/styles' import {useAnalytics} from 'lib/analytics/analytics' import {ComposeIcon2} from 'lib/icons' import {useSetTitle} from 'lib/hooks/useSetTitle' import {combinedDisplayName} from 'lib/strings/display-names' import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll' import {FeedDescriptor} from '#/state/queries/post-feed' import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {useProfileQuery} from '#/state/queries/profile' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession} from '#/state/session' import {useModerationOpts} from '#/state/queries/preferences' import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {cleanError} from '#/lib/strings/errors' import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn' import {useQueryClient} from '@tanstack/react-query' import {useComposerControls} from '#/state/shell/composer' type Props = NativeStackScreenProps export const ProfileScreen = withAuthRequired(function ProfileScreenImpl({ route, }: Props) { const {currentAccount} = useSession() const name = route.params.name === 'me' ? currentAccount?.did : route.params.name const moderationOpts = useModerationOpts() const { data: resolvedDid, error: resolveError, refetch: refetchDid, isFetching: isFetchingDid, } = useResolveDidQuery(name) const { data: profile, dataUpdatedAt, error: profileError, refetch: refetchProfile, isFetching: isFetchingProfile, } = useProfileQuery({ did: resolvedDid?.did, }) const onPressTryAgain = React.useCallback(() => { if (resolveError) { refetchDid() } else { refetchProfile() } }, [resolveError, refetchDid, refetchProfile]) if (isFetchingDid || isFetchingProfile || !moderationOpts) { return ( ) } if (resolveError || profileError) { return ( ) } if (profile && moderationOpts) { return ( ) } // should never happen return ( ) }) function ProfileScreenLoaded({ profile: profileUnshadowed, dataUpdatedAt, moderationOpts, hideBackButton, }: { profile: AppBskyActorDefs.ProfileViewDetailed dataUpdatedAt: number moderationOpts: ModerationOpts hideBackButton: boolean }) { const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) const store = useStores() const {currentAccount} = useSession() const setMinimalShellMode = useSetMinimalShellMode() const {openComposer} = useComposerControls() const {screen, track} = useAnalytics() const [currentPage, setCurrentPage] = React.useState(0) const {_} = useLingui() const viewSelectorRef = React.useRef(null) const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() const extraInfoQuery = useProfileExtraInfoQuery(profile.did) useSetTitle(combinedDisplayName(profile)) const moderation = useMemo( () => moderateProfile(profile, moderationOpts), [profile, moderationOpts], ) const isMe = profile.did === currentAccount?.did const showLikesTab = isMe const showFeedsTab = isMe || extraInfoQuery.data?.hasFeedgens const showListsTab = isMe || extraInfoQuery.data?.hasLists const sectionTitles = useMemo(() => { return [ 'Posts', 'Posts & Replies', 'Media', showLikesTab ? 'Likes' : undefined, showFeedsTab ? 'Feeds' : undefined, showListsTab ? 'Lists' : undefined, ].filter(Boolean) as string[] }, [showLikesTab, showFeedsTab, showListsTab]) /* - todo - feeds - lists */ useFocusEffect( React.useCallback(() => { setMinimalShellMode(false) screen('Profile') const softResetSub = store.onScreenSoftReset(() => { viewSelectorRef.current?.scrollToTop() }) return () => softResetSub.remove() }, [store, viewSelectorRef, setMinimalShellMode, screen]), ) useFocusEffect( React.useCallback(() => { setDrawerSwipeDisabled(currentPage > 0) return () => { setDrawerSwipeDisabled(false) } }, [setDrawerSwipeDisabled, currentPage]), ) // events // = const onPressCompose = React.useCallback(() => { track('ProfileScreen:PressCompose') const mention = profile.handle === currentAccount?.handle || profile.handle === 'handle.invalid' ? undefined : profile.handle openComposer({mention}) }, [openComposer, currentAccount, track, profile]) const onPageSelected = React.useCallback( i => { setCurrentPage(i) }, [setCurrentPage], ) // rendering // = const renderHeader = React.useCallback(() => { return ( ) }, [profile, moderation, hideBackButton]) return ( {({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => ( )} {({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => ( )} {({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => ( )} {showLikesTab ? ({ onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef, }) => ( ) : null} {showFeedsTab ? ({onScroll, headerHeight, isFocused, scrollElRef}) => ( ) : null} {showListsTab ? ({onScroll, headerHeight, isFocused, scrollElRef}) => ( ) : null} } accessibilityRole="button" accessibilityLabel={_(msg`New post`)} accessibilityHint="" /> ) } interface FeedSectionProps { feed: FeedDescriptor onScroll: OnScrollHandler headerHeight: number isFocused: boolean isScrolledDown: boolean scrollElRef: any /* TODO */ } const FeedSection = React.forwardRef( function FeedSectionImpl( {feed, onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}, ref, ) { const queryClient = useQueryClient() const [hasNew, setHasNew] = React.useState(false) const onScrollToTop = React.useCallback(() => { scrollElRef.current?.scrollToOffset({offset: -headerHeight}) queryClient.invalidateQueries({queryKey: FEED_RQKEY(feed)}) setHasNew(false) }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) React.useImperativeHandle(ref, () => ({ scrollToTop: onScrollToTop, })) const renderPostsEmpty = React.useCallback(() => { return }, []) return ( {(isScrolledDown || hasNew) && ( )} ) }, ) const styles = StyleSheet.create({ container: { flexDirection: 'column', height: '100%', }, loading: { paddingVertical: 10, paddingHorizontal: 14, }, emptyState: { paddingVertical: 40, }, loadingMoreFooter: { paddingVertical: 20, }, endItem: { paddingTop: 20, paddingBottom: 30, color: colors.gray5, textAlign: 'center', }, })