mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
8import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
9import {getLabelStrings} from '#/lib/moderation/useLabelInfo'
10import {
11 usePreferencesQuery,
12 usePreferencesSetContentLabelMutation,
13} from '#/state/queries/preferences'
14import {atoms as a, useBreakpoints, useTheme} from '#/alf'
15import * as ToggleButton from '#/components/forms/ToggleButton'
16import {InlineLinkText} from '#/components/Link'
17import {Text} from '#/components/Typography'
18import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo'
19
20export function Outer({children}: React.PropsWithChildren<{}>) {
21 return (
22 <View
23 style={[
24 a.flex_row,
25 a.gap_md,
26 a.px_lg,
27 a.py_lg,
28 a.justify_between,
29 a.flex_wrap,
30 ]}>
31 {children}
32 </View>
33 )
34}
35
36export function Content({
37 children,
38 name,
39 description,
40}: React.PropsWithChildren<{
41 name: string
42 description: string
43}>) {
44 const t = useTheme()
45 const {gtPhone} = useBreakpoints()
46
47 return (
48 <View style={[a.gap_xs, a.flex_1]}>
49 <Text style={[a.font_bold, gtPhone ? a.text_sm : a.text_md]}>{name}</Text>
50 <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
51 {description}
52 </Text>
53
54 {children}
55 </View>
56 )
57}
58
59export function Buttons({
60 name,
61 values,
62 onChange,
63 ignoreLabel,
64 warnLabel,
65 hideLabel,
66}: {
67 name: string
68 values: ToggleButton.GroupProps['values']
69 onChange: ToggleButton.GroupProps['onChange']
70 ignoreLabel?: string
71 warnLabel?: string
72 hideLabel?: string
73}) {
74 const {_} = useLingui()
75 const {gtPhone} = useBreakpoints()
76
77 return (
78 <View style={[{minHeight: 35}, gtPhone ? undefined : a.w_full]}>
79 <ToggleButton.Group
80 label={_(
81 msg`Configure content filtering setting for category: ${name}`,
82 )}
83 values={values}
84 onChange={onChange}>
85 {ignoreLabel && (
86 <ToggleButton.Button name="ignore" label={ignoreLabel}>
87 <ToggleButton.ButtonText>{ignoreLabel}</ToggleButton.ButtonText>
88 </ToggleButton.Button>
89 )}
90 {warnLabel && (
91 <ToggleButton.Button name="warn" label={warnLabel}>
92 <ToggleButton.ButtonText>{warnLabel}</ToggleButton.ButtonText>
93 </ToggleButton.Button>
94 )}
95 {hideLabel && (
96 <ToggleButton.Button name="hide" label={hideLabel}>
97 <ToggleButton.ButtonText>{hideLabel}</ToggleButton.ButtonText>
98 </ToggleButton.Button>
99 )}
100 </ToggleButton.Group>
101 </View>
102 )
103}
104
105/**
106 * For use on the global Moderation screen to set prefs for a "global" label,
107 * not scoped to a single labeler.
108 */
109export function GlobalLabelPreference({
110 labelDefinition,
111 disabled,
112}: {
113 labelDefinition: InterpretedLabelValueDefinition
114 disabled?: boolean
115}) {
116 const {_} = useLingui()
117
118 const {identifier} = labelDefinition
119 const {data: preferences} = usePreferencesQuery()
120 const {mutate, variables} = usePreferencesSetContentLabelMutation()
121 const savedPref = preferences?.moderationPrefs.labels[identifier]
122 const pref = variables?.visibility ?? savedPref ?? 'warn'
123
124 const allLabelStrings = useGlobalLabelStrings()
125 const labelStrings =
126 labelDefinition.identifier in allLabelStrings
127 ? allLabelStrings[labelDefinition.identifier]
128 : {
129 name: labelDefinition.identifier,
130 description: `Labeled "${labelDefinition.identifier}"`,
131 }
132
133 const labelOptions = {
134 hide: _(msg`Hide`),
135 warn: _(msg`Warn`),
136 ignore: _(msg`Show`),
137 }
138
139 return (
140 <Outer>
141 <Content
142 name={labelStrings.name}
143 description={labelStrings.description}
144 />
145 {!disabled && (
146 <Buttons
147 name={labelStrings.name.toLowerCase()}
148 values={[pref]}
149 onChange={values => {
150 mutate({
151 label: identifier,
152 visibility: values[0] as LabelPreference,
153 labelerDid: undefined,
154 })
155 }}
156 ignoreLabel={labelOptions.ignore}
157 warnLabel={labelOptions.warn}
158 hideLabel={labelOptions.hide}
159 />
160 )}
161 </Outer>
162 )
163}
164
165/**
166 * For use on individual labeler pages
167 */
168export function LabelerLabelPreference({
169 labelDefinition,
170 disabled,
171 labelerDid,
172}: {
173 labelDefinition: InterpretedLabelValueDefinition
174 disabled?: boolean
175 labelerDid?: string
176}) {
177 const {_, i18n} = useLingui()
178 const t = useTheme()
179 const {gtPhone} = useBreakpoints()
180
181 const isGlobalLabel = !labelDefinition.definedBy
182 const {identifier} = labelDefinition
183 const {data: preferences} = usePreferencesQuery()
184 const {mutate, variables} = usePreferencesSetContentLabelMutation()
185 const savedPref =
186 labelerDid && !isGlobalLabel
187 ? preferences?.moderationPrefs.labelers.find(l => l.did === labelerDid)
188 ?.labels[identifier]
189 : preferences?.moderationPrefs.labels[identifier]
190 const pref =
191 variables?.visibility ??
192 savedPref ??
193 labelDefinition.defaultSetting ??
194 'warn'
195
196 // does the 'warn' setting make sense for this label?
197 const canWarn = !(
198 labelDefinition.blurs === 'none' && labelDefinition.severity === 'none'
199 )
200 // is this label adult only?
201 const adultOnly = labelDefinition.flags.includes('adult')
202 // is this label disabled because it's adult only?
203 const adultDisabled =
204 adultOnly && !preferences?.moderationPrefs.adultContentEnabled
205 // are there any reasons we cant configure this label here?
206 const cantConfigure = isGlobalLabel || adultDisabled
207 const showConfig = !disabled && (gtPhone || !cantConfigure)
208
209 // adjust the pref based on whether warn is available
210 let prefAdjusted = pref
211 if (adultDisabled) {
212 prefAdjusted = 'hide'
213 } else if (!canWarn && pref === 'warn') {
214 prefAdjusted = 'ignore'
215 }
216
217 // grab localized descriptions of the label and its settings
218 const currentPrefLabel = useLabelBehaviorDescription(
219 labelDefinition,
220 prefAdjusted,
221 )
222 const hideLabel = useLabelBehaviorDescription(labelDefinition, 'hide')
223 const warnLabel = useLabelBehaviorDescription(labelDefinition, 'warn')
224 const ignoreLabel = useLabelBehaviorDescription(labelDefinition, 'ignore')
225 const globalLabelStrings = useGlobalLabelStrings()
226 const labelStrings = getLabelStrings(
227 i18n.locale,
228 globalLabelStrings,
229 labelDefinition,
230 )
231
232 return (
233 <Outer>
234 <Content name={labelStrings.name} description={labelStrings.description}>
235 {cantConfigure && (
236 <View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}>
237 <CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} />
238
239 <Text
240 style={[t.atoms.text_contrast_medium, a.font_semibold, a.italic]}>
241 {adultDisabled ? (
242 <Trans>Adult content is disabled.</Trans>
243 ) : isGlobalLabel ? (
244 <Trans>
245 Configured in{' '}
246 <InlineLinkText
247 label={_(msg`moderation settings`)}
248 to="/moderation"
249 style={a.text_sm}>
250 moderation settings
251 </InlineLinkText>
252 .
253 </Trans>
254 ) : null}
255 </Text>
256 </View>
257 )}
258 </Content>
259
260 {showConfig && (
261 <View style={[gtPhone ? undefined : a.w_full]}>
262 {cantConfigure ? (
263 <View
264 style={[
265 {minHeight: 35},
266 a.px_md,
267 a.py_md,
268 a.rounded_sm,
269 a.border,
270 t.atoms.border_contrast_low,
271 ]}>
272 <Text style={[a.font_bold, t.atoms.text_contrast_low]}>
273 {currentPrefLabel}
274 </Text>
275 </View>
276 ) : (
277 <Buttons
278 name={labelStrings.name.toLowerCase()}
279 values={[pref]}
280 onChange={values => {
281 mutate({
282 label: identifier,
283 visibility: values[0] as LabelPreference,
284 labelerDid,
285 })
286 }}
287 ignoreLabel={ignoreLabel}
288 warnLabel={canWarn ? warnLabel : undefined}
289 hideLabel={hideLabel}
290 />
291 )}
292 </View>
293 )}
294 </Outer>
295 )
296}