Move moderation card from the profile header to a tab
This commit is contained in:
@@ -5,9 +5,10 @@ import {Trans} from '@lingui/macro'
|
|||||||
import {atoms as a, useTheme, ViewStyleProp, flatten} from '#/alf'
|
import {atoms as a, useTheme, ViewStyleProp, flatten} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand'
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {pluralize} from '#/lib/strings/helpers'
|
||||||
|
|
||||||
export function Outer({
|
export function Outer({
|
||||||
children,
|
children,
|
||||||
@@ -64,10 +65,12 @@ export function Content({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
handle,
|
handle,
|
||||||
|
likeCount,
|
||||||
}: {
|
}: {
|
||||||
title: string
|
title: string
|
||||||
description?: string
|
description?: string
|
||||||
handle: string
|
handle: string
|
||||||
|
likeCount?: number
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
@@ -85,9 +88,23 @@ export function Content({
|
|||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{typeof likeCount === 'number' && (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
a.text_sm,
|
||||||
|
{fontWeight: '500'},
|
||||||
|
a.mt_sm,
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
Liked by {likeCount || 0} {pluralize(likeCount || 0, 'user')}
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<RaisingHand size="xl" style={[a.z_10]} fill={t.palette.primary_500} />
|
<ChevronRight size="md" style={[a.z_10]} fill={t.palette.contrast_500} />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,14 +56,10 @@ import {Shadow} from '#/state/cache/types'
|
|||||||
import {useRequireAuth} from '#/state/session'
|
import {useRequireAuth} from '#/state/session'
|
||||||
import {LabelsOnMe} from '../util/moderation/LabelsOnMe'
|
import {LabelsOnMe} from '../util/moderation/LabelsOnMe'
|
||||||
import {useProfileShadow} from 'state/cache/profile-shadow'
|
import {useProfileShadow} from 'state/cache/profile-shadow'
|
||||||
import * as ModerationServiceCard from '#/components/ModerationServiceCard'
|
|
||||||
import {getModerationServiceTitle} from '#/lib/moderation'
|
|
||||||
import {useOpenGlobalDialog} from '#/components/dialogs'
|
import {useOpenGlobalDialog} from '#/components/dialogs'
|
||||||
import {ReportDialog} from '#/components/dialogs/ReportDialog'
|
import {ReportDialog} from '#/components/dialogs/ReportDialog'
|
||||||
import {NEW_REPORT_DIALOG_ENABLED} from '#/lib/build-flags'
|
import {NEW_REPORT_DIALOG_ENABLED} from '#/lib/build-flags'
|
||||||
|
|
||||||
import {useTheme} from '#/alf'
|
|
||||||
|
|
||||||
let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
|
let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
return (
|
return (
|
||||||
@@ -99,7 +95,6 @@ let ProfileHeader = ({
|
|||||||
hideBackButton = false,
|
hideBackButton = false,
|
||||||
isPlaceholderProfile,
|
isPlaceholderProfile,
|
||||||
}: Props): React.ReactNode => {
|
}: Props): React.ReactNode => {
|
||||||
const t = useTheme()
|
|
||||||
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
|
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
|
||||||
useProfileShadow(profileUnshadowed)
|
useProfileShadow(profileUnshadowed)
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
@@ -637,31 +632,6 @@ let ProfileHeader = ({
|
|||||||
{isMe && (
|
{isMe && (
|
||||||
<LabelsOnMe details={{did: profile.did}} labels={profile.labels} />
|
<LabelsOnMe details={{did: profile.did}} labels={profile.labels} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ModerationServiceCard.Loader
|
|
||||||
did={profile.did}
|
|
||||||
component={({modservice}) => (
|
|
||||||
<ModerationServiceCard.Link modservice={modservice}>
|
|
||||||
{ctx => (
|
|
||||||
<ModerationServiceCard.Card.Outer
|
|
||||||
style={[
|
|
||||||
...(ctx.focused || ctx.hovered
|
|
||||||
? [t.atoms.bg_contrast_50]
|
|
||||||
: []),
|
|
||||||
]}>
|
|
||||||
<ModerationServiceCard.Card.Content
|
|
||||||
title={getModerationServiceTitle({
|
|
||||||
displayName: modservice.creator.displayName,
|
|
||||||
handle: modservice.creator.handle,
|
|
||||||
})}
|
|
||||||
description={modservice.description}
|
|
||||||
handle={modservice.creator.handle}
|
|
||||||
/>
|
|
||||||
</ModerationServiceCard.Card.Outer>
|
|
||||||
)}
|
|
||||||
</ModerationServiceCard.Link>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{showSuggestedFollows && (
|
{showSuggestedFollows && (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||||
import {CenteredView} from '../com/util/Views'
|
import {CenteredView, ScrollView} from '../com/util/Views'
|
||||||
import {ListRef} from '../com/util/List'
|
import {ListRef} from '../com/util/List'
|
||||||
import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
|
import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
|
||||||
import {Feed} from 'view/com/posts/Feed'
|
import {Feed} from 'view/com/posts/Feed'
|
||||||
@@ -49,6 +49,10 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||||
|
|
||||||
|
import {useTheme, atoms as a} from '#/alf'
|
||||||
|
import * as ModerationServiceCard from '#/components/ModerationServiceCard'
|
||||||
|
import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand'
|
||||||
|
|
||||||
interface SectionRef {
|
interface SectionRef {
|
||||||
scrollToTop: () => void
|
scrollToTop: () => void
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,7 @@ function ProfileScreenLoaded({
|
|||||||
const likesSectionRef = React.useRef<SectionRef>(null)
|
const likesSectionRef = React.useRef<SectionRef>(null)
|
||||||
const feedsSectionRef = React.useRef<SectionRef>(null)
|
const feedsSectionRef = React.useRef<SectionRef>(null)
|
||||||
const listsSectionRef = React.useRef<SectionRef>(null)
|
const listsSectionRef = React.useRef<SectionRef>(null)
|
||||||
|
const moderationSectionRef = React.useRef<SectionRef>(null)
|
||||||
|
|
||||||
useSetTitle(combinedDisplayName(profile))
|
useSetTitle(combinedDisplayName(profile))
|
||||||
|
|
||||||
@@ -175,6 +180,9 @@ function ProfileScreenLoaded({
|
|||||||
const showLikesTab = isMe
|
const showLikesTab = isMe
|
||||||
const showFeedsTab = hasSession && (isMe || extraInfoQuery.data?.hasFeedgens)
|
const showFeedsTab = hasSession && (isMe || extraInfoQuery.data?.hasFeedgens)
|
||||||
const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists)
|
const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists)
|
||||||
|
const showModerationTab =
|
||||||
|
hasSession && (isMe || true) /* TODO true should be associated modservice */
|
||||||
|
|
||||||
const sectionTitles = useMemo<string[]>(() => {
|
const sectionTitles = useMemo<string[]>(() => {
|
||||||
return [
|
return [
|
||||||
_(msg`Posts`),
|
_(msg`Posts`),
|
||||||
@@ -183,8 +191,16 @@ function ProfileScreenLoaded({
|
|||||||
showLikesTab ? _(msg`Likes`) : undefined,
|
showLikesTab ? _(msg`Likes`) : undefined,
|
||||||
showFeedsTab ? _(msg`Feeds`) : undefined,
|
showFeedsTab ? _(msg`Feeds`) : undefined,
|
||||||
showListsTab ? _(msg`Lists`) : undefined,
|
showListsTab ? _(msg`Lists`) : undefined,
|
||||||
|
showModerationTab ? _(msg`Moderation`) : undefined,
|
||||||
].filter(Boolean) as string[]
|
].filter(Boolean) as string[]
|
||||||
}, [showRepliesTab, showLikesTab, showFeedsTab, showListsTab, _])
|
}, [
|
||||||
|
showRepliesTab,
|
||||||
|
showLikesTab,
|
||||||
|
showFeedsTab,
|
||||||
|
showListsTab,
|
||||||
|
showModerationTab,
|
||||||
|
_,
|
||||||
|
])
|
||||||
|
|
||||||
let nextIndex = 0
|
let nextIndex = 0
|
||||||
const postsIndex = nextIndex++
|
const postsIndex = nextIndex++
|
||||||
@@ -205,6 +221,10 @@ function ProfileScreenLoaded({
|
|||||||
if (showListsTab) {
|
if (showListsTab) {
|
||||||
listsIndex = nextIndex++
|
listsIndex = nextIndex++
|
||||||
}
|
}
|
||||||
|
let moderationIndex: number | null = null
|
||||||
|
if (showModerationTab) {
|
||||||
|
moderationIndex = nextIndex++
|
||||||
|
}
|
||||||
|
|
||||||
const scrollSectionToTop = React.useCallback(
|
const scrollSectionToTop = React.useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
@@ -220,9 +240,19 @@ function ProfileScreenLoaded({
|
|||||||
feedsSectionRef.current?.scrollToTop()
|
feedsSectionRef.current?.scrollToTop()
|
||||||
} else if (index === listsIndex) {
|
} else if (index === listsIndex) {
|
||||||
listsSectionRef.current?.scrollToTop()
|
listsSectionRef.current?.scrollToTop()
|
||||||
|
} else if (index === moderationIndex) {
|
||||||
|
moderationSectionRef.current?.scrollToTop()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[postsIndex, repliesIndex, mediaIndex, likesIndex, feedsIndex, listsIndex],
|
[
|
||||||
|
postsIndex,
|
||||||
|
repliesIndex,
|
||||||
|
mediaIndex,
|
||||||
|
likesIndex,
|
||||||
|
feedsIndex,
|
||||||
|
listsIndex,
|
||||||
|
moderationIndex,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@@ -372,6 +402,17 @@ function ProfileScreenLoaded({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
|
{showModerationTab
|
||||||
|
? ({headerHeight, isFocused, scrollElRef}) => (
|
||||||
|
<ModerationSection
|
||||||
|
// ref={moderationSectionRef}
|
||||||
|
did={profile.did}
|
||||||
|
scrollElRef={scrollElRef as ListRef}
|
||||||
|
headerOffset={headerHeight}
|
||||||
|
enabled={isFocused}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
</PagerWithHeader>
|
</PagerWithHeader>
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<FAB
|
<FAB
|
||||||
@@ -458,6 +499,55 @@ function ProfileEndOfFeed() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ModerationSection({did}: {did: string}) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<ModerationServiceCard.Loader
|
||||||
|
did={did}
|
||||||
|
component={({modservice}) => (
|
||||||
|
<ModerationServiceCard.Link modservice={modservice}>
|
||||||
|
{ctx => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_md,
|
||||||
|
a.p_md,
|
||||||
|
a.border_t,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
...(ctx.focused || ctx.hovered
|
||||||
|
? [t.atoms.bg_contrast_25]
|
||||||
|
: []),
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{backgroundColor: t.palette.negative_25},
|
||||||
|
a.p_lg,
|
||||||
|
a.rounded_sm,
|
||||||
|
]}>
|
||||||
|
<RaisingHand
|
||||||
|
width={36}
|
||||||
|
style={[a.z_10]}
|
||||||
|
fill={t.palette.negative_500}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<ModerationServiceCard.Card.Content
|
||||||
|
title="Moderation service"
|
||||||
|
description={modservice.description}
|
||||||
|
handle={modservice.creator.handle}
|
||||||
|
likeCount={modservice.likeCount}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ModerationServiceCard.Link>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function useRichText(text: string): [RichTextAPI, boolean] {
|
function useRichText(text: string): [RichTextAPI, boolean] {
|
||||||
const [prevText, setPrevText] = React.useState(text)
|
const [prevText, setPrevText] = React.useState(text)
|
||||||
const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text}))
|
const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text}))
|
||||||
|
|||||||
Reference in New Issue
Block a user