Wire up more of the modservice data to profiles

This commit is contained in:
Paul Frazee
2024-02-21 11:28:49 -08:00
parent 67d0dde5b7
commit acd9532331
6 changed files with 65 additions and 101 deletions
@@ -2,6 +2,7 @@ import React, {memo, useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import { import {
AppBskyActorDefs, AppBskyActorDefs,
AppBskyModerationDefs,
ModerationOpts, ModerationOpts,
moderateProfile, moderateProfile,
RichText as RichTextAPI, RichText as RichTextAPI,
@@ -36,6 +37,7 @@ import {
interface Props { interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
modservice: AppBskyModerationDefs.ModServiceViewDetailed
descriptionRT: RichTextAPI | null descriptionRT: RichTextAPI | null
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
hideBackButton?: boolean hideBackButton?: boolean
@@ -44,6 +46,7 @@ interface Props {
let ProfileHeaderModerator = ({ let ProfileHeaderModerator = ({
profile: profileUnshadowed, profile: profileUnshadowed,
modservice,
descriptionRT, descriptionRT,
moderationOpts, moderationOpts,
hideBackButton = false, hideBackButton = false,
@@ -66,16 +69,18 @@ let ProfileHeaderModerator = ({
const isSubscribed = const isSubscribed =
variables?.subscribe ?? variables?.subscribe ??
preferences?.moderationOpts.mods.find(mod => mod.did === profile.did) preferences?.moderationOpts.mods.find(mod => mod.did === profile.did)
const modservice = {likeCount: 0} // TODO
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation() const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
const {mutateAsync: unlikeMod, isPending: isUnlikePending} = const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
useUnlikeMutation() useUnlikeMutation()
const [likeUri, setLikeUri] = React.useState<string>( const [likeUri, setLikeUri] = React.useState<string>(
/* TODO modservice.viewer?.like ||*/ '', modservice.viewer?.like || '',
) )
const isLiked = !!likeUri const isLiked = !!likeUri
const onToggleLiked = React.useCallback(async () => { const onToggleLiked = React.useCallback(async () => {
if (!modservice) {
return
}
try { try {
Haptics.default() Haptics.default()
@@ -84,10 +89,9 @@ let ProfileHeaderModerator = ({
track('CustomFeed:Unlike') track('CustomFeed:Unlike')
setLikeUri('') setLikeUri('')
} else { } else {
// TODO const res = await likeMod({uri: modservice.uri, cid: modservice.cid})
// const res = await likeMod({uri: modservice.uri, cid: modservice.cid})
track('CustomFeed:Like') track('CustomFeed:Like')
// setLikeUri(res.uri) setLikeUri(res.uri)
} }
} catch (e: any) { } catch (e: any) {
Toast.show( Toast.show(
@@ -97,7 +101,7 @@ let ProfileHeaderModerator = ({
) )
logger.error(`Failed to toggle labeler like`, {message: e.message}) logger.error(`Failed to toggle labeler like`, {message: e.message})
} }
}, [likeUri, isLiked, likeMod, unlikeMod, track, _]) }, [modservice, likeUri, isLiked, likeMod, unlikeMod, track, _])
const onPressEditProfile = React.useCallback(() => { const onPressEditProfile = React.useCallback(() => {
track('ProfileHeader:EditProfileButtonClicked') track('ProfileHeader:EditProfileButtonClicked')
@@ -28,7 +28,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {InlineLink} from '#/components/Link' import {InlineLink} from '#/components/Link'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText, ButtonIcon} from '#/components/Button'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {ProfileHeaderShell} from './Shell' import {ProfileHeaderShell} from './Shell'
import {ProfileHeaderDropdownBtn} from './DropdownBtn' import {ProfileHeaderDropdownBtn} from './DropdownBtn'
@@ -36,6 +36,8 @@ import {ProfileHeaderDisplayName} from './DisplayName'
import {ProfileHeaderHandle} from './Handle' import {ProfileHeaderHandle} from './Handle'
import {ProfileHeaderSuggestedFollows} from '#/view/com/profile/ProfileHeaderSuggestedFollows' import {ProfileHeaderSuggestedFollows} from '#/view/com/profile/ProfileHeaderSuggestedFollows'
import {RichText} from 'view/com/util/text/RichText' import {RichText} from 'view/com/util/text/RichText'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
interface Props { interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
@@ -224,7 +226,11 @@ let ProfileHeaderStandard = ({
onPress={ onPress={
profile.viewer?.following ? onPressUnfollow : onPressFollow profile.viewer?.following ? onPressUnfollow : onPressFollow
} }
style={a.rounded_full}> style={[a.rounded_full, a.gap_xs]}>
<ButtonIcon
position="left"
icon={profile.viewer?.following ? Check : Plus}
/>
<ButtonText> <ButtonText>
{profile.viewer?.following ? ( {profile.viewer?.following ? (
<Trans>Following</Trans> <Trans>Following</Trans>
+6 -1
View File
@@ -2,6 +2,7 @@ import React, {memo} from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import { import {
AppBskyActorDefs, AppBskyActorDefs,
AppBskyModerationDefs,
ModerationOpts, ModerationOpts,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
@@ -33,6 +34,7 @@ export {ProfileHeaderLoading}
interface Props { interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
modservice: AppBskyModerationDefs.ModServiceViewDetailed | undefined
descriptionRT: RichTextAPI | null descriptionRT: RichTextAPI | null
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
hideBackButton?: boolean hideBackButton?: boolean
@@ -41,7 +43,10 @@ interface Props {
let ProfileHeader = (props: Props): React.ReactNode => { let ProfileHeader = (props: Props): React.ReactNode => {
if (props.profile.associated?.modservice) { if (props.profile.associated?.modservice) {
return <ProfileHeaderModerator {...props} /> if (!props.modservice) {
return <ProfileHeaderLoading />
}
return <ProfileHeaderModerator {...props} modservice={props.modservice} />
} }
return <ProfileHeaderStandard {...props} /> return <ProfileHeaderStandard {...props} />
} }
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {AppBskyModerationDefs, AppBskyActorDefs} from '@atproto/api' import {AppBskyModerationDefs, ModerationOpts} from '@atproto/api'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useSafeAreaFrame} from 'react-native-safe-area-context' import {useSafeAreaFrame} from 'react-native-safe-area-context'
@@ -11,14 +11,7 @@ import {Haptics} from '#/lib/haptics'
import {useAnalytics} from '#/lib/analytics/analytics' import {useAnalytics} from '#/lib/analytics/analytics'
import {CenteredView, ScrollView} from '#/view/com/util/Views' import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {logger} from '#/logger' import {logger} from '#/logger'
import { import {useModServiceEnableMutation} from '#/state/queries/modservice'
useModServiceInfoQuery,
useModServiceEnableMutation,
} from '#/state/queries/modservice'
import {
UsePreferencesQueryResponse,
usePreferencesQuery,
} from '#/state/queries/preferences'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
@@ -33,28 +26,21 @@ import * as Toggle from '#/components/forms/Toggle'
import {ErrorState} from '#/screens/ProfileModerationService/ErrorState' import {ErrorState} from '#/screens/ProfileModerationService/ErrorState'
import {PreferenceRow} from '#/screens/ProfileModerationService/PreferenceRow' import {PreferenceRow} from '#/screens/ProfileModerationService/PreferenceRow'
export function ProfileFiltersSection({ export function ProfileContentFiltersSection({
profile, modServiceQuery,
moderationOpts,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed modServiceQuery: {
data: AppBskyModerationDefs.ModServiceViewDetailed | undefined
isLoading: boolean
error: Error | null
}
moderationOpts: ModerationOpts
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {height: minHeight} = useSafeAreaFrame() const {height: minHeight} = useSafeAreaFrame()
const { const {isLoading, error, data: modservice} = modServiceQuery
isLoading: isPreferencesLoading,
error: preferencesError,
data: preferences,
} = usePreferencesQuery()
const {
isLoading: isModServiceLoading,
error: modServiceError,
data: modservice,
} = useModServiceInfoQuery({did: profile.did})
const isLoading = isPreferencesLoading || isModServiceLoading
const error = preferencesError || modServiceError
return ( return (
<CenteredView> <CenteredView>
<View <View
@@ -71,7 +57,7 @@ export function ProfileFiltersSection({
<View style={[a.w_full, a.align_center]}> <View style={[a.w_full, a.align_center]}>
<Loader size="xl" /> <Loader size="xl" />
</View> </View>
) : error || !(preferences && modservice) ? ( ) : error || !modservice ? (
<ErrorState <ErrorState
error={ error={
error?.toString() || error?.toString() ||
@@ -79,8 +65,8 @@ export function ProfileFiltersSection({
} }
/> />
) : ( ) : (
<ProfileFiltersSectionInner <ProfileContentFiltersSectionInner
preferences={preferences} moderationOpts={moderationOpts}
modservice={modservice} modservice={modservice}
/> />
)} )}
@@ -89,11 +75,11 @@ export function ProfileFiltersSection({
) )
} }
export function ProfileFiltersSectionInner({ export function ProfileContentFiltersSectionInner({
preferences, moderationOpts,
modservice, modservice,
}: { }: {
preferences: UsePreferencesQueryResponse moderationOpts: ModerationOpts
modservice: AppBskyModerationDefs.ModServiceViewDetailed modservice: AppBskyModerationDefs.ModServiceViewDetailed
}) { }) {
const t = useTheme() const t = useTheme()
@@ -119,16 +105,14 @@ export function ProfileFiltersSectionInner({
) )
}, [modservice.policies.labelValues]) }, [modservice.policies.labelValues])
const modservicePreferences = React.useMemo(() => { const modservicePreferences = React.useMemo(() => {
return preferences.moderationOpts.mods.find( return moderationOpts.mods.find(p => p.did === modservice.creator.did)
p => p.did === modservice.creator.did, }, [modservice.creator.did, moderationOpts.mods])
) const isSubscribed = moderationOpts.mods.find(
}, [modservice.creator.did, preferences.moderationOpts.mods])
const isSubscribed = preferences.moderationOpts.mods.find(
mod => mod.did === modservice.creator.did, mod => mod.did === modservice.creator.did,
) )
const isEnabled = Boolean( const isEnabled = Boolean(
enabledVariables?.enabled ?? enabledVariables?.enabled ??
preferences.moderationOpts.mods.find( moderationOpts.mods.find(
mod => mod.did === modservice.creator.did && mod.enabled, mod => mod.did === modservice.creator.did && mod.enabled,
), ),
) )
+8 -2
View File
@@ -15,9 +15,15 @@ export const modServicesDetailedInfoQueryKey = (dids: string[]) => [
dids, dids,
] ]
export function useModServiceInfoQuery({did}: {did?: string}) { export function useModServiceInfoQuery({
did,
enabled,
}: {
did?: string
enabled?: boolean
}) {
return useQuery({ return useQuery({
enabled: !!did, enabled: !!did && enabled !== false,
queryKey: modServiceInfoQueryKey(did as string), queryKey: modServiceInfoQueryKey(did as string),
queryFn: async () => { queryFn: async () => {
const res = await getAgent().app.bsky.moderation.getService({ const res = await getAgent().app.bsky.moderation.getService({
+11 -52
View File
@@ -36,6 +36,7 @@ import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession, getAgent} from '#/state/session' import {useSession, getAgent} from '#/state/session'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info' import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info'
import {useModServiceInfoQuery} from '#/state/queries/modservice'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
@@ -52,7 +53,7 @@ import {isInvalidHandle} from '#/lib/strings/handles'
import {useTheme, atoms as a} from '#/alf' import {useTheme, atoms as a} from '#/alf'
import * as ModerationServiceCard from '#/components/ModerationServiceCard' import * as ModerationServiceCard from '#/components/ModerationServiceCard'
import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand' import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand'
import {ProfileFiltersSection} from '#/screens/Profile/FiltersSection' import {ProfileContentFiltersSection} from '#/screens/Profile/Sections/ContentFilters'
import {ProfileHeader as ProfileHeaderV2} from '#/screens/Profile/Header' import {ProfileHeader as ProfileHeaderV2} from '#/screens/Profile/Header'
interface SectionRef { interface SectionRef {
@@ -449,6 +450,10 @@ function ProfileScreenLoadedV2({
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
const modServiceQuery = useModServiceInfoQuery({
did: profile.did,
enabled: !!profile.associated?.modservice,
})
const [currentPage, setCurrentPage] = React.useState(0) const [currentPage, setCurrentPage] = React.useState(0)
const {_} = useLingui() const {_} = useLingui()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
@@ -616,6 +621,7 @@ function ProfileScreenLoadedV2({
return ( return (
<ProfileHeaderV2 <ProfileHeaderV2
profile={profile} profile={profile}
modservice={modServiceQuery.data}
descriptionRT={hasDescription ? descriptionRT : null} descriptionRT={hasDescription ? descriptionRT : null}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
hideBackButton={hideBackButton} hideBackButton={hideBackButton}
@@ -624,6 +630,7 @@ function ProfileScreenLoadedV2({
) )
}, [ }, [
profile, profile,
modServiceQuery,
descriptionRT, descriptionRT,
hasDescription, hasDescription,
moderationOpts, moderationOpts,
@@ -646,9 +653,10 @@ function ProfileScreenLoadedV2({
renderHeader={renderHeader}> renderHeader={renderHeader}>
{showFiltersTab {showFiltersTab
? ({headerHeight, isFocused, scrollElRef}) => ( ? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileFiltersSection <ProfileContentFiltersSection
// ref={moderationSectionRef} // ref={moderationSectionRef}
profile={profile} modServiceQuery={modServiceQuery}
moderationOpts={moderationOpts}
scrollElRef={scrollElRef as ListRef} scrollElRef={scrollElRef as ListRef}
headerOffset={headerHeight} headerOffset={headerHeight}
enabled={isFocused} enabled={isFocused}
@@ -860,55 +868,6 @@ function ModerationSection({did}: {did: string}) {
) )
} }
function FiltersSection({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}))