From 6432667f608fae447b59e41b9f8bb64b564205a1 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 9 Sep 2025 20:20:33 +0300 Subject: ALF lists screen (#8941) * alf list screens * relocate to `#/screens`, balkanize * use useBreakpoints * showCancel on subscribe menu * fix typo --- src/screens/ProfileList/index.tsx | 296 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 src/screens/ProfileList/index.tsx (limited to 'src/screens/ProfileList/index.tsx') diff --git a/src/screens/ProfileList/index.tsx b/src/screens/ProfileList/index.tsx new file mode 100644 index 000000000..b3928c3d0 --- /dev/null +++ b/src/screens/ProfileList/index.tsx @@ -0,0 +1,296 @@ +import {useCallback, useMemo, useRef} from 'react' +import {View} from 'react-native' +import {useAnimatedRef} from 'react-native-reanimated' +import { + AppBskyGraphDefs, + AtUri, + moderateUserList, + type ModerationOpts, +} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect, useIsFocused} from '@react-navigation/native' +import {useQueryClient} from '@tanstack/react-query' + +import {useOpenComposer} from '#/lib/hooks/useOpenComposer' +import {useSetTitle} from '#/lib/hooks/useSetTitle' +import {ComposeIcon2} from '#/lib/icons' +import { + type CommonNavigatorParams, + type NativeStackScreenProps, +} from '#/lib/routes/types' +import {cleanError} from '#/lib/strings/errors' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useListQuery} from '#/state/queries/list' +import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' +import { + usePreferencesQuery, + type UsePreferencesQueryResponse, +} from '#/state/queries/preferences' +import {useResolveUriQuery} from '#/state/queries/resolve-uri' +import {truncateAndInvalidate} from '#/state/queries/util' +import {useSession} from '#/state/session' +import {useSetMinimalShellMode} from '#/state/shell' +import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader' +import {FAB} from '#/view/com/util/fab/FAB' +import {type ListRef} from '#/view/com/util/List' +import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' +import {atoms as a, platform} from '#/alf' +import {useDialogControl} from '#/components/Dialog' +import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' +import * as Layout from '#/components/Layout' +import {Loader} from '#/components/Loader' +import * as Hider from '#/components/moderation/Hider' +import {AboutSection} from './AboutSection' +import {ErrorScreen} from './components/ErrorScreen' +import {Header} from './components/Header' +import {FeedSection} from './FeedSection' + +interface SectionRef { + scrollToTop: () => void +} + +type Props = NativeStackScreenProps +export function ProfileListScreen(props: Props) { + return ( + + + + ) +} + +function ProfileListScreenInner(props: Props) { + const {_} = useLingui() + const {name: handleOrDid, rkey} = props.route.params + const {data: resolvedUri, error: resolveError} = useResolveUriQuery( + AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), + ) + const {data: preferences} = usePreferencesQuery() + const {data: list, error: listError} = useListQuery(resolvedUri?.uri) + const moderationOpts = useModerationOpts() + + if (resolveError) { + return ( + <> + + + + + Could not load list + + + + + + + + + ) + } + if (listError) { + return ( + <> + + + + + Could not load list + + + + + + + + + ) + } + + return resolvedUri && list && moderationOpts && preferences ? ( + + ) : ( + <> + + + + + + + + + + ) +} + +function ProfileListScreenLoaded({ + route, + uri, + list, + moderationOpts, + preferences, +}: Props & { + uri: string + list: AppBskyGraphDefs.ListView + moderationOpts: ModerationOpts + preferences: UsePreferencesQueryResponse +}) { + const {_} = useLingui() + const queryClient = useQueryClient() + const {openComposer} = useOpenComposer() + const setMinimalShellMode = useSetMinimalShellMode() + const {currentAccount} = useSession() + const {rkey} = route.params + const feedSectionRef = useRef(null) + const aboutSectionRef = useRef(null) + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST + const isScreenFocused = useIsFocused() + const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 + const isOwner = currentAccount?.did === list.creator.did + const scrollElRef = useAnimatedRef() + const addUserDialogControl = useDialogControl() + const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] + + const moderation = useMemo(() => { + return moderateUserList(list, moderationOpts) + }, [list, moderationOpts]) + + useSetTitle(isHidden ? _(msg`List Hidden`) : list.name) + + useFocusEffect( + useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + const onChangeMembers = () => { + if (isCurateList) { + truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`)) + } + } + + const onCurrentPageSelected = useCallback( + (index: number) => { + if (index === 0) { + feedSectionRef.current?.scrollToTop() + } else if (index === 1) { + aboutSectionRef.current?.scrollToTop() + } + }, + [feedSectionRef], + ) + + const renderHeader = useCallback(() => { + return
+ }, [rkey, list, preferences]) + + if (isCurateList) { + return ( + + + + + + + + {({headerHeight, scrollElRef, isFocused}) => ( + + )} + {({headerHeight, scrollElRef}) => ( + + )} + + openComposer({})} + icon={ + + } + accessibilityRole="button" + accessibilityLabel={_(msg`New post`)} + accessibilityHint="" + /> + + + + + ) + } + return ( + + + + + + + {renderHeader()} + + openComposer({})} + icon={ + + } + accessibilityRole="button" + accessibilityLabel={_(msg`New post`)} + accessibilityHint="" + /> + + + + + ) +} -- cgit 1.4.1