WIP dialog
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user