diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/screens/StarterPack/StarterPackScreen.tsx | 1 | ||||
-rw-r--r-- | src/view/com/pager/PagerWithHeader.tsx | 4 | ||||
-rw-r--r-- | src/view/com/profile/ProfileSubpageHeader.tsx | 65 | ||||
-rw-r--r-- | src/view/screens/ProfileList.tsx | 317 |
4 files changed, 179 insertions, 208 deletions
diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index 1b2f61bd5..3a3e4234f 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -407,6 +407,7 @@ function Header({ isOwner={isOwn} avatar={undefined} creator={creator} + purpose="app.bsky.graph.defs#referencelist" avatarType="starter-pack"> {hasSession ? ( <View style={[a.flex_row, a.gap_sm, a.align_center]}> diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx index dcf141f84..1746d2ca1 100644 --- a/src/view/com/pager/PagerWithHeader.tsx +++ b/src/view/com/pager/PagerWithHeader.tsx @@ -21,6 +21,7 @@ import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {ScrollProvider} from '#/lib/ScrollContext' import {isIOS} from '#/platform/detection' import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager' +import {useTheme} from '#/alf' import {ListMethods} from '../util/List' import {PagerHeaderProvider} from './PagerHeaderContext' import {TabBar} from './TabBar' @@ -256,6 +257,7 @@ let PagerTabBar = ({ dragProgress: SharedValue<number> dragState: SharedValue<'idle' | 'dragging' | 'settling'> }): React.ReactNode => { + const t = useTheme() const [minimumHeaderHeight, setMinimumHeaderHeight] = React.useState(0) const headerTransform = useAnimatedStyle(() => { const translateY = @@ -277,7 +279,7 @@ let PagerTabBar = ({ return ( <Animated.View pointerEvents={isIOS ? 'auto' : 'box-none'} - style={[styles.tabBarMobile, headerTransform]}> + style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}> <View ref={headerRef} pointerEvents={isIOS ? 'auto' : 'box-none'} diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index cd11611a8..b0cf4d10e 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,6 +1,7 @@ import React from 'react' import {Pressable, View} from 'react-native' import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' +import {AppBskyGraphDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' @@ -26,6 +27,7 @@ export function ProfileSubpageHeader({ title, avatar, isOwner, + purpose, creator, avatarType, children, @@ -35,6 +37,7 @@ export function ProfileSubpageHeader({ title: string | undefined avatar: string | undefined isOwner: boolean | undefined + purpose: AppBskyGraphDefs.ListPurpose | undefined creator: | { did: string @@ -105,7 +108,7 @@ export function ProfileSubpageHeader({ alignItems: 'flex-start', gap: 10, paddingTop: 14, - paddingBottom: 6, + paddingBottom: 14, paddingHorizontal: isMobile ? 12 : 14, }}> <View ref={aviRef} collapsable={false}> @@ -123,7 +126,7 @@ export function ProfileSubpageHeader({ )} </Pressable> </View> - <View style={{flex: 1}}> + <View style={{flex: 1, gap: 4}}> {isLoading ? ( <LoadingPlaceholder width={200} @@ -142,24 +145,50 @@ export function ProfileSubpageHeader({ /> )} - {isLoading ? ( + {isLoading || !creator ? ( <LoadingPlaceholder width={50} height={8} /> ) : ( - <Text type="xl" style={[pal.textLight]} numberOfLines={1}> - {!creator ? ( - <Trans>by —</Trans> - ) : isOwner ? ( - <Trans>by you</Trans> - ) : ( - <Trans> - by{' '} - <TextLink - text={sanitizeHandle(creator.handle, '@')} - href={makeProfileLink(creator)} - style={pal.textLight} - /> - </Trans> - )} + <Text type="lg" style={[pal.textLight]} numberOfLines={1}> + {purpose === 'app.bsky.graph.defs#curatelist' ? ( + isOwner ? ( + <Trans>List by you</Trans> + ) : ( + <Trans> + List by{' '} + <TextLink + text={sanitizeHandle(creator.handle || '', '@')} + href={makeProfileLink(creator)} + style={pal.textLight} + /> + </Trans> + ) + ) : purpose === 'app.bsky.graph.defs#modlist' ? ( + isOwner ? ( + <Trans>Moderation list by you</Trans> + ) : ( + <Trans> + Moderation list by{' '} + <TextLink + text={sanitizeHandle(creator.handle || '', '@')} + href={makeProfileLink(creator)} + style={pal.textLight} + /> + </Trans> + ) + ) : purpose === 'app.bsky.graph.defs#referencelist' ? ( + isOwner ? ( + <Trans>Starter pack by you</Trans> + ) : ( + <Trans> + Starter pack by{' '} + <TextLink + text={sanitizeHandle(creator.handle || '', '@')} + href={makeProfileLink(creator)} + style={pal.textLight} + /> + </Trans> + ) + ) : null} </Text> )} </View> diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index 27ede80a3..2e661ff46 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useMemo} from 'react' import {Pressable, StyleSheet, View} from 'react-native' +import {useAnimatedRef} from 'react-native-reanimated' import { AppBskyGraphDefs, AtUri, @@ -19,12 +20,11 @@ import {usePalette} from '#/lib/hooks/usePalette' import {useSetTitle} from '#/lib/hooks/useSetTitle' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {ComposeIcon2} from '#/lib/icons' -import {makeListLink, makeProfileLink} from '#/lib/routes/links' +import {makeListLink} from '#/lib/routes/links' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {NavigationProp} from '#/lib/routes/types' import {shareUrl} from '#/lib/sharing' import {cleanError} from '#/lib/strings/errors' -import {sanitizeHandle} from '#/lib/strings/handles' import {toShareUrl} from '#/lib/strings/url-helpers' import {s} from '#/lib/styles' import {logger} from '#/logger' @@ -63,14 +63,13 @@ import { DropdownItem, NativeDropdown, } from '#/view/com/util/forms/NativeDropdown' -import {TextLink} from '#/view/com/util/Link' import {ListRef} from '#/view/com/util/List' import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' import {LoadingScreen} from '#/view/com/util/LoadingScreen' import {Text} from '#/view/com/util/text/Text' import * as Toast from '#/view/com/util/Toast' import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a} from '#/alf' import {useDialogControl} from '#/components/Dialog' import * as Layout from '#/components/Layout' import * as Hider from '#/components/moderation/Hider' @@ -78,8 +77,7 @@ import * as Prompt from '#/components/Prompt' import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {RichText} from '#/components/RichText' -const SECTION_TITLES_CURATE = ['Posts', 'About'] -const SECTION_TITLES_MOD = ['About'] +const SECTION_TITLES_CURATE = ['Posts', 'People'] interface SectionRef { scrollToTop: () => void @@ -161,6 +159,7 @@ function ProfileListScreenLoaded({ const isScreenFocused = useIsFocused() const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 const isOwner = currentAccount?.did === list.creator.did + const scrollElRef = useAnimatedRef() const moderation = React.useMemo(() => { return moderateUserList(list, moderationOpts) @@ -259,19 +258,13 @@ function ProfileListScreenLoaded({ </Hider.Mask> <Hider.Content> <View style={s.hContentRegion}> - <PagerWithHeader - items={SECTION_TITLES_MOD} - isHeaderReady={true} - renderHeader={renderHeader}> - {({headerHeight, scrollElRef}) => ( - <AboutSection - list={list} - scrollElRef={scrollElRef as ListRef} - onPressAddUser={onPressAddUser} - headerHeight={headerHeight} - /> - )} - </PagerWithHeader> + <Layout.Center>{renderHeader()}</Layout.Center> + <AboutSection + list={list} + scrollElRef={scrollElRef as ListRef} + onPressAddUser={onPressAddUser} + headerHeight={0} + /> <FAB testID="composeFAB" onPress={() => openComposer({})} @@ -652,101 +645,124 @@ function Header({ ] }, [_, subscribeMutePromptControl.open, subscribeBlockPromptControl.open]) + const descriptionRT = useMemo( + () => + list.description + ? new RichTextAPI({ + text: list.description, + facets: list.descriptionFacets, + }) + : undefined, + [list], + ) + return ( - <ProfileSubpageHeader - href={makeListLink(list.creator.handle || list.creator.did || '', rkey)} - title={list.name} - avatar={list.avatar} - isOwner={list.creator.did === currentAccount?.did} - creator={list.creator} - avatarType="list"> - <ReportDialog - control={reportDialogControl} - params={{ - type: 'list', - uri: list.uri, - cid: list.cid, - }} - /> - {isCurateList ? ( - <Button - testID={isPinned ? 'unpinBtn' : 'pinBtn'} - type={isPinned ? 'default' : 'inverted'} - label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)} - onPress={onTogglePinned} - disabled={isPending} + <> + <ProfileSubpageHeader + href={makeListLink(list.creator.handle || list.creator.did || '', rkey)} + title={list.name} + avatar={list.avatar} + isOwner={list.creator.did === currentAccount?.did} + creator={list.creator} + purpose={list.purpose} + avatarType="list"> + <ReportDialog + control={reportDialogControl} + params={{ + type: 'list', + uri: list.uri, + cid: list.cid, + }} /> - ) : isModList ? ( - isBlocking ? ( + {isCurateList ? ( <Button - testID="unblockBtn" - type="default" - label={_(msg`Unblock`)} - onPress={onUnsubscribeBlock} + testID={isPinned ? 'unpinBtn' : 'pinBtn'} + type={isPinned ? 'default' : 'inverted'} + label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)} + onPress={onTogglePinned} + disabled={isPending} /> - ) : isMuting ? ( - <Button - testID="unmuteBtn" - type="default" - label={_(msg`Unmute`)} - onPress={onUnsubscribeMute} - /> - ) : ( - <NativeDropdown - testID="subscribeBtn" - items={subscribeDropdownItems} - accessibilityLabel={_(msg`Subscribe to this list`)} - accessibilityHint=""> - <View style={[palInverted.view, styles.btn]}> - <Text style={palInverted.text}> - <Trans>Subscribe</Trans> - </Text> - </View> - </NativeDropdown> - ) - ) : null} - <NativeDropdown - testID="headerDropdownBtn" - items={dropdownItems} - accessibilityLabel={_(msg`More options`)} - accessibilityHint=""> - <View style={[pal.viewLight, styles.btn]}> - <FontAwesomeIcon icon="ellipsis" size={20} color={pal.colors.text} /> - </View> - </NativeDropdown> + ) : isModList ? ( + isBlocking ? ( + <Button + testID="unblockBtn" + type="default" + label={_(msg`Unblock`)} + onPress={onUnsubscribeBlock} + /> + ) : isMuting ? ( + <Button + testID="unmuteBtn" + type="default" + label={_(msg`Unmute`)} + onPress={onUnsubscribeMute} + /> + ) : ( + <NativeDropdown + testID="subscribeBtn" + items={subscribeDropdownItems} + accessibilityLabel={_(msg`Subscribe to this list`)} + accessibilityHint=""> + <View style={[palInverted.view, styles.btn]}> + <Text style={palInverted.text}> + <Trans>Subscribe</Trans> + </Text> + </View> + </NativeDropdown> + ) + ) : null} + <NativeDropdown + testID="headerDropdownBtn" + items={dropdownItems} + accessibilityLabel={_(msg`More options`)} + accessibilityHint=""> + <View style={[pal.viewLight, styles.btn]}> + <FontAwesomeIcon + icon="ellipsis" + size={20} + color={pal.colors.text} + /> + </View> + </NativeDropdown> - <Prompt.Basic - control={deleteListPromptControl} - title={_(msg`Delete this list?`)} - description={_( - msg`If you delete this list, you won't be able to recover it.`, - )} - onConfirm={onPressDelete} - confirmButtonCta={_(msg`Delete`)} - confirmButtonColor="negative" - /> + <Prompt.Basic + control={deleteListPromptControl} + title={_(msg`Delete this list?`)} + description={_( + msg`If you delete this list, you won't be able to recover it.`, + )} + onConfirm={onPressDelete} + confirmButtonCta={_(msg`Delete`)} + confirmButtonColor="negative" + /> - <Prompt.Basic - control={subscribeMutePromptControl} - title={_(msg`Mute these accounts?`)} - description={_( - msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, - )} - onConfirm={onSubscribeMute} - confirmButtonCta={_(msg`Mute list`)} - /> + <Prompt.Basic + control={subscribeMutePromptControl} + title={_(msg`Mute these accounts?`)} + description={_( + msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, + )} + onConfirm={onSubscribeMute} + confirmButtonCta={_(msg`Mute list`)} + /> - <Prompt.Basic - control={subscribeBlockPromptControl} - title={_(msg`Block these accounts?`)} - description={_( - msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, - )} - onConfirm={onSubscribeBlock} - confirmButtonCta={_(msg`Block list`)} - confirmButtonColor="negative" - /> - </ProfileSubpageHeader> + <Prompt.Basic + control={subscribeBlockPromptControl} + title={_(msg`Block these accounts?`)} + description={_( + msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, + )} + onConfirm={onSubscribeBlock} + confirmButtonCta={_(msg`Block list`)} + confirmButtonColor="negative" + /> + </ProfileSubpageHeader> + {descriptionRT ? ( + <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}> + <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} /> + </View> + ) : null} + </> ) } @@ -825,25 +841,12 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( ref, ) { const pal = usePalette('default') - const t = useTheme() const {_} = useLingui() const {isMobile} = useWebMediaQueries() const {currentAccount} = useSession() const [isScrolledDown, setIsScrolledDown] = React.useState(false) - const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist' const isOwner = list.creator.did === currentAccount?.did - const descriptionRT = useMemo( - () => - list.description - ? new RichTextAPI({ - text: list.description, - facets: list.descriptionFacets, - }) - : undefined, - [list], - ) - const onScrollToTop = useCallback(() => { scrollElRef.current?.scrollToOffset({ animated: isNative, @@ -856,59 +859,11 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( })) const renderHeader = React.useCallback(() => { + if (!isOwner) { + return <View /> + } return ( - <View> - <View - style={[ - { - borderTopWidth: StyleSheet.hairlineWidth, - padding: isMobile ? 14 : 20, - gap: 12, - }, - pal.border, - ]}> - {descriptionRT ? ( - <RichText - testID="listDescription" - style={[a.text_md]} - value={descriptionRT} - /> - ) : ( - <Text - testID="listDescriptionEmpty" - type="lg" - style={[{fontStyle: 'italic'}, pal.textLight]}> - <Trans>No description</Trans> - </Text> - )} - <Text type="md" style={[pal.textLight]} numberOfLines={1}> - {isCurateList ? ( - isOwner ? ( - <Trans>User list by you</Trans> - ) : ( - <Trans> - User list by{' '} - <TextLink - text={sanitizeHandle(list.creator.handle || '', '@')} - href={makeProfileLink(list.creator)} - style={pal.textLight} - /> - </Trans> - ) - ) : isOwner ? ( - <Trans>Moderation list by you</Trans> - ) : ( - <Trans> - Moderation list by{' '} - <TextLink - text={sanitizeHandle(list.creator.handle || '', '@')} - href={makeProfileLink(list.creator)} - style={pal.textLight} - /> - </Trans> - )} - </Text> - </View> + <View style={a.pt_lg}> <View style={[ { @@ -919,9 +874,6 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( paddingBottom: isMobile ? 14 : 18, }, ]}> - <Text type="lg-bold" style={t.atoms.text}> - <Trans>Users</Trans> - </Text> {isOwner && ( <Pressable testID="addUserBtn" @@ -943,20 +895,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( </View> </View> ) - }, [ - isMobile, - pal.border, - pal.textLight, - pal.colors.link, - pal.link, - descriptionRT, - isCurateList, - isOwner, - list.creator, - t.atoms.text, - _, - onPressAddUser, - ]) + }, [isMobile, pal.colors.link, pal.link, isOwner, _, onPressAddUser]) const renderEmptyState = useCallback(() => { return ( |