Hook up data to ReportDialog

This commit is contained in:
Eric Bailey
2024-02-14 20:35:26 -06:00
parent 5587fe73cb
commit 46d95350fb
5 changed files with 156 additions and 42 deletions
+136 -39
View File
@@ -2,7 +2,7 @@ import React from 'react'
import {View, Dimensions} from 'react-native' import {View, Dimensions} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {LABEL_GROUPS, LabelGroupDefinition} from '@atproto/api' import {AppBskyModerationDefs, LabelGroupDefinition} from '@atproto/api'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {atoms as a, useTheme, tokens, native} from '#/alf' import {atoms as a, useTheme, tokens, native} from '#/alf'
@@ -25,10 +25,16 @@ 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'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {GradientFill} from '#/components/GradientFill' import {GradientFill} from '#/components/GradientFill'
import * as TextField from '#/components/forms/TextField'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useModServicesDetailedInfoQuery} from '#/state/queries/modservice'
import {
getLabelGroupsFromLabels,
getModerationServiceTitle,
useConfigurableLabelGroups,
} from '#/lib/moderation'
export type ReportDialogProps = export type ReportDialogProps =
| { | {
@@ -41,7 +47,11 @@ export type ReportDialogProps =
did: string did: string
} }
function LabelGroupButton({labelGroup}: {labelGroup: string}) { function LabelGroupButton({
labelGroup,
}: {
labelGroup: LabelGroupDefinition['id']
}) {
const t = useTheme() const t = useTheme()
const {hovered, focused, pressed} = useButtonContext() const {hovered, focused, pressed} = useButtonContext()
const labelGroupStrings = useLabelGroupStrings() const labelGroupStrings = useLabelGroupStrings()
@@ -143,22 +153,89 @@ function ModServiceToggle({title}: {title: string}) {
) )
} }
function SubmitViewLoader({
children,
}: {
children: (props: {
labelers: AppBskyModerationDefs.ModServiceViewDetailed[]
labelGroupToModServiceMap: Record<LabelGroupDefinition['id'], string[]>
}) => React.ReactNode
}) {
const {
isLoading: isPreferencesLoading,
error: preferencesError,
data: preferences,
} = usePreferencesQuery()
const {
isLoading: isModServicesLoading,
data: modservices,
error: modservicesError,
} = useModServicesDetailedInfoQuery({
dids: preferences ? preferences.moderationOpts.mods.map(m => m.did) : [],
})
const labelGroupToModServiceMap = React.useMemo(() => {
if (!modservices) return {}
const groups: Partial<
Record<
LabelGroupDefinition['id'],
AppBskyModerationDefs.ModServiceViewDetailed[]
>
> = {}
for (const modservice of modservices) {
const labelGroups = getLabelGroupsFromLabels(
modservice.policies.labelValues,
)
for (const group of labelGroups) {
const g = (groups[group.id] = groups[group.id] || [])
g.push(modservice)
}
}
return groups
}, [modservices])
const isLoading = isPreferencesLoading || isModServicesLoading
const error = preferencesError || modservicesError
return isLoading ? (
<View style={[{height: 500}]}>
<Loader />
</View>
) : error || !(preferences && modservices) ? null : ( // TODO
children({
labelers: modservices,
// TODO mismatched types
// @ts-ignore
labelGroupToModServiceMap,
})
)
}
function SubmitView({ function SubmitView({
selectedLabelGroup, selectedLabelGroup,
goBack, goBack,
onSubmitComplete, onSubmitComplete,
labelGroupToModServiceMap,
}: { }: {
selectedLabelGroup: string selectedLabelGroup: LabelGroupDefinition['id']
goBack: () => void goBack: () => void
onSubmitComplete: () => void onSubmitComplete: () => void
labelers: AppBskyModerationDefs.ModServiceViewDetailed[]
labelGroupToModServiceMap: Record<
LabelGroupDefinition['id'],
AppBskyModerationDefs.ModServiceViewDetailed[]
>
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const labelGroupStrings = useLabelGroupStrings() const labelGroupStrings = useLabelGroupStrings()
const groupInfoStrings = labelGroupStrings[selectedLabelGroup] const groupInfoStrings = labelGroupStrings[selectedLabelGroup]
const [selectedServices, setSelectedServices] = React.useState<string[]>([])
const [details, setDetails] = React.useState<string>('') const [details, setDetails] = React.useState<string>('')
const [submitting, setSubmitting] = React.useState<boolean>(false) const [submitting, setSubmitting] = React.useState<boolean>(false)
const [selectedServices, setSelectedServices] = React.useState<string[]>([])
const supportedLabelers = labelGroupToModServiceMap[selectedLabelGroup]
const submit = React.useCallback(async () => { const submit = React.useCallback(async () => {
setSubmitting(true) setSubmitting(true)
@@ -208,22 +285,35 @@ function SubmitView({
Select the moderation service(s) to report to Select the moderation service(s) to report to
</Text> </Text>
<Toggle.Group {supportedLabelers ? (
label="Select mod services" <Toggle.Group
values={selectedServices} label="Select mod services"
onChange={setSelectedServices}> values={selectedServices}
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}> onChange={setSelectedServices}>
<Toggle.Item name="bluesky" label="Bluesky"> <View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
<ModServiceToggle title="Bluesky" /> {supportedLabelers.map(labeler => {
</Toggle.Item> const title = getModerationServiceTitle({
<Toggle.Item name="contraption" label="The Contraption"> displayName: labeler.creator.displayName,
<ModServiceToggle title="The Contraption" /> handle: labeler.creator.handle,
</Toggle.Item> })
<Toggle.Item name="safety" label="Safety Corp"> return (
<ModServiceToggle title="Safety Corp" /> <Toggle.Item
</Toggle.Item> key={labeler.creator.did}
name={labeler.creator.did}
label={title}>
<ModServiceToggle title={title} />
</Toggle.Item>
)
})}
</View>
</Toggle.Group>
) : (
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
<Text style={[a.italic, t.atoms.text_contrast_medium]}>
None of your subscribed labelers support this content type.
</Text>
</View> </View>
</Toggle.Group> )}
</View> </View>
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
<Text style={[t.atoms.text_contrast_medium]}> <Text style={[t.atoms.text_contrast_medium]}>
@@ -231,13 +321,14 @@ function SubmitView({
</Text> </Text>
<View style={[a.relative, a.w_full]}> <View style={[a.relative, a.w_full]}>
<TextField.Input <Dialog.Input
multiline multiline
value={details} value={details}
onChangeText={setDetails} onChangeText={setDetails}
label="Text field" label="Text field"
style={{paddingRight: 60}} style={{paddingRight: 60}}
numberOfLines={6} numberOfLines={6}
disabled={!supportedLabelers}
/> />
<View <View
@@ -260,10 +351,11 @@ function SubmitView({
<View style={[a.align_end]}> <View style={[a.align_end]}>
<Button <Button
size="large" size="large"
variant="gradient" variant="solid"
color="gradient_midnight" color="primary"
label={_(msg`Submit`)} label={_(msg`Submit`)}
onPress={submit}> onPress={submit}
disabled={!supportedLabelers}>
<ButtonText>Submit</ButtonText> <ButtonText>Submit</ButtonText>
{submitting && <ButtonIcon icon={Loader} />} {submitting && <ButtonIcon icon={Loader} />}
</Button> </Button>
@@ -284,7 +376,9 @@ export function ReportDialog({
const {_} = useLingui() const {_} = useLingui()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const control = Dialog.useDialogControl() const control = Dialog.useDialogControl()
const [selectedLabelGroup, setSelectedLabelGroup] = React.useState<string>('') const [selectedLabelGroup, setSelectedLabelGroup] = React.useState<
LabelGroupDefinition['id'] | undefined
>()
const labelGroupStrings = useLabelGroupStrings() const labelGroupStrings = useLabelGroupStrings()
// REQUIRED CLEANUP // REQUIRED CLEANUP
@@ -305,11 +399,7 @@ export function ReportDialog({
} }
}, [_, params.type]) }, [_, params.type])
const groups = React.useMemo< const groups = useConfigurableLabelGroups()
[keyof typeof LABEL_GROUPS, LabelGroupDefinition][]
>(() => {
return Object.entries(LABEL_GROUPS).filter(([, def]) => def.configurable)
}, [])
return ( return (
<Dialog.Outer <Dialog.Outer
@@ -325,11 +415,18 @@ export function ReportDialog({
<Dialog.ScrollableInner label="Report Dialog"> <Dialog.ScrollableInner label="Report Dialog">
{selectedLabelGroup ? ( {selectedLabelGroup ? (
<SubmitView <SubmitViewLoader>
selectedLabelGroup={selectedLabelGroup} {props => (
goBack={() => setSelectedLabelGroup('')} // TODO same types mismatch
onSubmitComplete={control.close} // @ts-ignore
/> <SubmitView
{...props}
selectedLabelGroup={selectedLabelGroup}
goBack={() => setSelectedLabelGroup(undefined)}
onSubmitComplete={control.close}
/>
)}
</SubmitViewLoader>
) : ( ) : (
<View style={[a.gap_lg]}> <View style={[a.gap_lg]}>
<View style={[a.justify_center, a.gap_sm]}> <View style={[a.justify_center, a.gap_sm]}>
@@ -342,14 +439,14 @@ export function ReportDialog({
<Divider /> <Divider />
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}> <View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
{groups.map(([name, def]) => { {groups.map(def => {
const groupStrings = labelGroupStrings[name] const groupStrings = labelGroupStrings[def.id]
return ( return (
<Button <Button
key={def.id} key={def.id}
label={_(msg`Create report for ${groupStrings.name}`)} label={_(msg`Create report for ${groupStrings.name}`)}
onPress={() => setSelectedLabelGroup(name)}> onPress={() => setSelectedLabelGroup(def.id)}>
<LabelGroupButton labelGroup={name} /> <LabelGroupButton labelGroup={def.id} />
</Button> </Button>
) )
})} })}
+1
View File
@@ -149,6 +149,7 @@ export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & {
value: string value: string
onChangeText: (value: string) => void onChangeText: (value: string) => void
isInvalid?: boolean isInvalid?: boolean
disabled?: boolean
} }
export function createInput(Component: typeof TextInput) { export function createInput(Component: typeof TextInput) {
+2 -2
View File
@@ -23,7 +23,7 @@ import {
usePreferencesQuery, usePreferencesQuery,
useSetContentLabelMutation, useSetContentLabelMutation,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {useModServicesInfoQuery} from '#/state/queries/modservice' import {useModServicesDetailedInfoQuery} from '#/state/queries/modservice'
import {useTheme, atoms as a, useBreakpoints} from '#/alf' import {useTheme, atoms as a, useBreakpoints} from '#/alf'
import {Divider} from '#/components/Divider' import {Divider} from '#/components/Divider'
@@ -79,7 +79,7 @@ function ModerationScreenIntermediate({
isLoading: isModServicesLoading, isLoading: isModServicesLoading,
data: modservices, data: modservices,
// error: modservicesError, // error: modservicesError,
} = useModServicesInfoQuery({ } = useModServicesDetailedInfoQuery({
dids: preferences.moderationOpts.mods.map(m => m.did), dids: preferences.moderationOpts.mods.map(m => m.did),
}) })
+16 -1
View File
@@ -7,7 +7,11 @@ import {preferencesQueryKey} from '#/state/queries/preferences'
export const modServiceInfoQueryKey = (did: string) => ['mod-service-info', did] export const modServiceInfoQueryKey = (did: string) => ['mod-service-info', did]
export const modServicesInfoQueryKey = (dids: string[]) => [ export const modServicesInfoQueryKey = (dids: string[]) => [
'mod-service-info', 'mod-services-info',
dids,
]
export const modServicesDetailedInfoQueryKey = (dids: string[]) => [
'mod-services-detailed-info',
dids, dids,
] ]
@@ -23,7 +27,18 @@ export function useModServiceInfoQuery({did}: {did: string}) {
export function useModServicesInfoQuery({dids}: {dids: string[]}) { export function useModServicesInfoQuery({dids}: {dids: string[]}) {
return useQuery({ return useQuery({
enabled: !!dids.length,
queryKey: modServicesInfoQueryKey(dids), queryKey: modServicesInfoQueryKey(dids),
queryFn: async () => {
const res = await getAgent().app.bsky.moderation.getServices({dids})
return res.data.views
},
})
}
export function useModServicesDetailedInfoQuery({dids}: {dids: string[]}) {
return useQuery({
queryKey: modServicesDetailedInfoQueryKey(dids),
queryFn: async () => { queryFn: async () => {
const views: AppBskyModerationDefs.ModServiceViewDetailed[] = [] const views: AppBskyModerationDefs.ModServiceViewDetailed[] = []
+1
View File
@@ -62,6 +62,7 @@ export function Forms() {
value={value} value={value}
onChangeText={setValue} onChangeText={setValue}
label="Text field" label="Text field"
disabled
/> />
</View> </View>