WIP dialog

This commit is contained in:
Eric Bailey
2024-08-21 10:25:56 -05:00
parent abbeef7b8d
commit ba099ca2bd
3 changed files with 138 additions and 157 deletions
+3 -1
View File
@@ -220,8 +220,10 @@ export function NameAndHandlePlaceholder() {
export function Description({ export function Description({
profile: profileUnshadowed, profile: profileUnshadowed,
numberOfLines = 3,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
numberOfLines?: number
}) { }) {
const profile = useProfileShadow(profileUnshadowed) const profile = useProfileShadow(profileUnshadowed)
const {description} = profile const {description} = profile
@@ -244,7 +246,7 @@ export function Description({
<RichText <RichText
value={rt} value={rt}
style={[a.leading_snug]} style={[a.leading_snug]}
numberOfLines={3} numberOfLines={numberOfLines}
disableLinks disableLinks
/> />
</View> </View>
@@ -6,7 +6,6 @@ import {
ModerationOpts, ModerationOpts,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -23,11 +22,12 @@ import {useRequireAuth, useSession} from '#/state/session'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {useProfileShadow} from 'state/cache/profile-shadow' import {useProfileShadow} from 'state/cache/profile-shadow'
import {ProfileHeaderSuggestedFollows} from '#/view/com/profile/ProfileHeaderSuggestedFollows' import {SimilarAccountsDialog} from '#/view/com/profile/ProfileHeaderSuggestedFollows'
import {ProfileMenu} from '#/view/com/profile/ProfileMenu' import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf' import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {MessageProfileButton} from '#/components/dms/MessageProfileButton' import {MessageProfileButton} from '#/components/dms/MessageProfileButton'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
@@ -59,7 +59,6 @@ let ProfileHeaderStandard = ({
}: Props): React.ReactNode => { }: Props): React.ReactNode => {
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
useProfileShadow(profileUnshadowed) useProfileShadow(profileUnshadowed)
const t = useTheme()
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
const {_} = useLingui() const {_} = useLingui()
const {openModal} = useModalControls() const {openModal} = useModalControls()
@@ -68,13 +67,13 @@ let ProfileHeaderStandard = ({
() => moderateProfile(profile, moderationOpts), () => moderateProfile(profile, moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
) )
const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false)
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
profile, profile,
'ProfileHeader', 'ProfileHeader',
) )
const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
const unblockPromptControl = Prompt.usePromptControl() const unblockPromptControl = Prompt.usePromptControl()
const similarAccountsControl = useDialogControl()
const requireAuth = useRequireAuth() const requireAuth = useRequireAuth()
const isBlockedUser = const isBlockedUser =
profile.viewer?.blocking || profile.viewer?.blocking ||
@@ -104,7 +103,7 @@ let ProfileHeaderStandard = ({
), ),
) )
if (gate('auto_show_prof_sugg_follows')) { if (gate('auto_show_prof_sugg_follows')) {
setShowSuggestedFollows(true) similarAccountsControl.open()
} }
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
@@ -205,32 +204,7 @@ let ProfileHeaderStandard = ({
) )
) : !profile.viewer?.blockedBy ? ( ) : !profile.viewer?.blockedBy ? (
<> <>
{hasSession && ( {hasSession && <MessageProfileButton profile={profile} />}
<>
<MessageProfileButton profile={profile} />
<Button
testID="suggestedFollowsBtn"
size="small"
color={showSuggestedFollows ? 'primary' : 'secondary'}
variant="solid"
shape="round"
onPress={() =>
setShowSuggestedFollows(!showSuggestedFollows)
}
label={_(msg`Show follows similar to ${profile.handle}`)}
style={{width: 36, height: 36}}>
<FontAwesomeIcon
icon="user-plus"
style={
showSuggestedFollows
? {color: t.palette.white}
: t.atoms.text
}
size={14}
/>
</Button>
</>
)}
<Button <Button
testID={profile.viewer?.following ? 'unfollowBtn' : 'followBtn'} testID={profile.viewer?.following ? 'unfollowBtn' : 'followBtn'}
@@ -295,19 +269,6 @@ let ProfileHeaderStandard = ({
</> </>
)} )}
</View> </View>
{showSuggestedFollows && (
<ProfileHeaderSuggestedFollows
actorDid={profile.did}
requestDismiss={() => {
if (showSuggestedFollows) {
setShowSuggestedFollows(false)
} else {
track('ProfileHeader:SuggestedFollowsOpened')
setShowSuggestedFollows(true)
}
}}
/>
)}
<Prompt.Basic <Prompt.Basic
control={unblockPromptControl} control={unblockPromptControl}
title={_(msg`Unblock Account?`)} title={_(msg`Unblock Account?`)}
@@ -320,6 +281,10 @@ let ProfileHeaderStandard = ({
} }
confirmButtonColor="negative" confirmButtonColor="negative"
/> />
<SimilarAccountsDialog
control={similarAccountsControl}
profile={profile}
/>
</ProfileHeaderShell> </ProfileHeaderShell>
) )
} }
@@ -1,23 +1,19 @@
import React from 'react' import React from 'react'
import {ScrollView, View} from 'react-native' import {View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
import {isWeb} from 'platform/detection'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import * as Dialog from '#/components/Dialog'
import {KnownFollowers} from '#/components/KnownFollowers'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
const OUTER_PADDING = a.p_md.padding
const INNER_PADDING = a.p_lg.padding
const TOTAL_HEIGHT = 232
const MOBILE_CARD_WIDTH = 300
function CardOuter({ function CardOuter({
children, children,
style, style,
@@ -30,10 +26,9 @@ function CardOuter({
a.p_lg, a.p_lg,
a.rounded_md, a.rounded_md,
a.border, a.border,
t.atoms.bg, t.atoms.bg_contrast_25,
t.atoms.border_contrast_low,
{ {
width: MOBILE_CARD_WIDTH, borderColor: t.atoms.bg_contrast_25.backgroundColor,
}, },
style, style,
]}> ]}>
@@ -56,106 +51,116 @@ export function SuggestedFollowPlaceholder() {
) )
} }
export function ProfileHeaderSuggestedFollows({ export type ProfileSuggestedFollowsProps = {
actorDid, control: Dialog.DialogOuterProps['control']
requestDismiss, profile: AppBskyActorDefs.ProfileViewDetailed
}: { }
actorDid: string
requestDismiss: () => void export function SimilarAccountsDialog({
}) { control,
profile,
}: ProfileSuggestedFollowsProps) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const [show, setShow] = React.useState(false)
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Accounts similar to @${profile.handle}`)}>
{show ? (
<SimilarAccounts profile={profile} />
) : (
<>
<Text style={[t.atoms.text, a.text_2xl, a.font_bold, a.mb_sm]}>
<Trans>Similar accounts</Trans>
</Text>
<Text
style={[
t.atoms.text_contrast_medium,
a.text_md,
a.mb_xl,
a.leading_snug,
]}>
<Trans>These are accounts similar to @{profile.handle}</Trans>
</Text>
<Button
label={_(msg`View accounts similar to @${profile.handle}`)}
size="medium"
variant="solid"
color="primary"
onPress={() => {
setShow(true)
}}>
<ButtonText>
<Trans>See similar accounts?</Trans>
</ButtonText>
</Button>
</>
)}
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
export function SimilarAccounts({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const t = useTheme()
const {isLoading: isSuggestionsLoading, data} = const {isLoading: isSuggestionsLoading, data} =
useSuggestedFollowsByActorQuery({ useSuggestedFollowsByActorQuery({
did: actorDid, did: profile.did,
}) })
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const isLoading = isSuggestionsLoading || !moderationOpts const isLoading = isSuggestionsLoading || !moderationOpts
return ( return (
<View <View>
style={{paddingVertical: OUTER_PADDING, height: TOTAL_HEIGHT}} <Text style={[t.atoms.text, a.text_2xl, a.font_bold, a.mb_sm]}>
pointerEvents="box-none"> <Trans>Similar accounts</Trans>
<View </Text>
pointerEvents="box-none" <Text
style={[ style={[
t.atoms.bg_contrast_25, t.atoms.text_contrast_medium,
{ a.text_md,
height: '100%', a.mb_xl,
paddingTop: INNER_PADDING / 2, a.leading_snug,
},
]}> ]}>
<View <Trans>These are accounts similar to @{profile.handle}</Trans>
pointerEvents="box-none" </Text>
style={[
a.flex_row,
a.justify_between,
a.align_center,
a.pt_xs,
{
paddingBottom: INNER_PADDING / 2,
paddingLeft: INNER_PADDING,
paddingRight: INNER_PADDING / 2,
},
]}>
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
<Trans>Similar accounts</Trans>
</Text>
<Button <View style={[a.gap_md]}>
onPress={requestDismiss} {isLoading ? (
hitSlop={10} <>
label={_(msg`Dismiss`)} <SuggestedFollowPlaceholder />
size="xsmall" <SuggestedFollowPlaceholder />
variant="ghost" <SuggestedFollowPlaceholder />
color="secondary" <SuggestedFollowPlaceholder />
shape="round"> <SuggestedFollowPlaceholder />
<ButtonIcon icon={X} size="sm" /> </>
</Button> ) : data ? (
</View> data.suggestions
.filter(s => (s.associated?.labeler ? false : true))
<ScrollView .map(profile => (
horizontal={true} <ProfileCard.Link
showsHorizontalScrollIndicator={isWeb} key={profile.did}
persistentScrollbar={true} profile={profile}
scrollIndicatorInsets={{bottom: 0}} onPress={() => {
snapToInterval={MOBILE_CARD_WIDTH + a.gap_sm.gap} logEvent('profile:header:suggestedFollowsCard:press', {})
decelerationRate="fast"> }}
<View style={[]}>
style={[ {({hovered, pressed}) => (
a.flex_row, <CardOuter
a.gap_sm, style={[
{ (hovered || pressed) && t.atoms.border_contrast_medium,
paddingHorizontal: INNER_PADDING, ]}>
paddingBottom: INNER_PADDING, <ProfileCard.Outer>
}, <View style={[a.gap_md]}>
]}> <View style={[a.gap_xs]}>
{isLoading ? (
<>
<SuggestedFollowPlaceholder />
<SuggestedFollowPlaceholder />
<SuggestedFollowPlaceholder />
<SuggestedFollowPlaceholder />
<SuggestedFollowPlaceholder />
</>
) : data ? (
data.suggestions
.filter(s => (s.associated?.labeler ? false : true))
.map(profile => (
<ProfileCard.Link
key={profile.did}
profile={profile}
onPress={() => {
logEvent('profile:header:suggestedFollowsCard:press', {})
}}
style={[a.flex_1]}>
{({hovered, pressed}) => (
<CardOuter
style={[
a.flex_1,
(hovered || pressed) && t.atoms.border_contrast_high,
]}>
<ProfileCard.Outer>
<ProfileCard.Header> <ProfileCard.Header>
<ProfileCard.Avatar <ProfileCard.Avatar
profile={profile} profile={profile}
@@ -173,17 +178,26 @@ export function ProfileHeaderSuggestedFollows({
shape="round" shape="round"
/> />
</ProfileCard.Header> </ProfileCard.Header>
<ProfileCard.Description profile={profile} /> <ProfileCard.Description
</ProfileCard.Outer> profile={profile}
</CardOuter> numberOfLines={2}
)} />
</ProfileCard.Link> </View>
))
) : ( <KnownFollowers
<View /> minimal
)} profile={profile}
</View> moderationOpts={moderationOpts}
</ScrollView> />
</View>
</ProfileCard.Outer>
</CardOuter>
)}
</ProfileCard.Link>
))
) : (
<View />
)}
</View> </View>
</View> </View>
) )