forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo, useState} from 'react'
2import {
3 LayoutAnimation,
4 type StyleProp,
5 View,
6 type ViewStyle,
7} from 'react-native'
8import {type ModerationUI} from '@atproto/api'
9import {msg, Trans} from '@lingui/macro'
10import {useLingui} from '@lingui/react'
11
12import {
13 ADULT_CONTENT_LABELS,
14 type AdultSelfLabel,
15 isJustAMute,
16} from '#/lib/moderation'
17import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
18import {getDefinition, getLabelStrings} from '#/lib/moderation/useLabelInfo'
19import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
20import {sanitizeDisplayName} from '#/lib/strings/display-names'
21import {useLabelDefinitions} from '#/state/preferences'
22import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
23import {Button} from '#/components/Button'
24import {
25 ModerationDetailsDialog,
26 useModerationDetailsDialogControl,
27} from '#/components/moderation/ModerationDetailsDialog'
28import {Text} from '#/components/Typography'
29
30export function ContentHider({
31 testID,
32 modui,
33 ignoreMute,
34 style,
35 activeStyle,
36 childContainerStyle,
37 children,
38}: {
39 testID?: string
40 modui: ModerationUI | undefined
41 ignoreMute?: boolean
42 style?: StyleProp<ViewStyle>
43 activeStyle?: StyleProp<ViewStyle>
44 childContainerStyle?: StyleProp<ViewStyle>
45 children?: React.ReactNode | ((props: {active: boolean}) => React.ReactNode)
46}) {
47 const blur = modui?.blurs[0]
48 if (!blur || (ignoreMute && isJustAMute(modui))) {
49 return (
50 <View testID={testID} style={style}>
51 {typeof children === 'function' ? children({active: false}) : children}
52 </View>
53 )
54 }
55 return (
56 <ContentHiderActive
57 testID={testID}
58 modui={modui}
59 style={[style, activeStyle]}
60 childContainerStyle={childContainerStyle}>
61 {typeof children === 'function' ? children({active: true}) : children}
62 </ContentHiderActive>
63 )
64}
65
66function ContentHiderActive({
67 testID,
68 modui,
69 style,
70 childContainerStyle,
71 children,
72}: {
73 testID?: string
74 modui: ModerationUI
75 style?: StyleProp<ViewStyle>
76 childContainerStyle?: StyleProp<ViewStyle>
77 children?: React.ReactNode
78}) {
79 const t = useTheme()
80 const {_} = useLingui()
81 const {gtMobile} = useBreakpoints()
82 const [override, setOverride] = useState(false)
83 const control = useModerationDetailsDialogControl()
84 const {labelDefs} = useLabelDefinitions()
85 const globalLabelStrings = useGlobalLabelStrings()
86 const {i18n} = useLingui()
87 const blur = modui?.blurs[0]
88 const desc = useModerationCauseDescription(blur)
89
90 const labelName = useMemo(() => {
91 if (!modui?.blurs || !blur) {
92 return undefined
93 }
94 if (
95 blur.type !== 'label' ||
96 (blur.type === 'label' && blur.source.type !== 'user')
97 ) {
98 if (desc.isSubjectAccount) {
99 return _(msg`${desc.name} (Account)`)
100 } else {
101 return desc.name
102 }
103 }
104
105 let hasAdultContentLabel = false
106 const selfBlurNames = modui.blurs
107 .filter(cause => {
108 if (cause.type !== 'label') {
109 return false
110 }
111 if (cause.source.type !== 'user') {
112 return false
113 }
114 if (ADULT_CONTENT_LABELS.includes(cause.label.val as AdultSelfLabel)) {
115 if (hasAdultContentLabel) {
116 return false
117 }
118 hasAdultContentLabel = true
119 }
120 return true
121 })
122 .slice(0, 2)
123 .map(cause => {
124 if (cause.type !== 'label') {
125 return
126 }
127
128 const def = cause.labelDef || getDefinition(labelDefs, cause.label)
129 if (def.identifier === 'porn' || def.identifier === 'sexual') {
130 return _(msg`Adult Content`)
131 }
132 return getLabelStrings(i18n.locale, globalLabelStrings, def).name
133 })
134
135 if (selfBlurNames.length === 0) {
136 return desc.name
137 }
138 return [...new Set(selfBlurNames)].join(', ')
139 }, [
140 _,
141 modui?.blurs,
142 blur,
143 desc.name,
144 desc.isSubjectAccount,
145 labelDefs,
146 i18n.locale,
147 globalLabelStrings,
148 ])
149
150 return (
151 <View testID={testID} style={[a.overflow_hidden, style]}>
152 <ModerationDetailsDialog control={control} modcause={blur} />
153
154 <Button
155 onPress={e => {
156 e.preventDefault()
157 e.stopPropagation()
158 if (!modui.noOverride) {
159 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
160 setOverride(v => !v)
161 } else {
162 control.open()
163 }
164 }}
165 label={desc.name}
166 accessibilityHint={
167 modui.noOverride
168 ? _(msg`Learn more about the moderation applied to this content`)
169 : override
170 ? _(msg`Hides the content`)
171 : _(msg`Shows the content`)
172 }>
173 {state => (
174 <View
175 style={[
176 a.flex_row,
177 a.w_full,
178 a.justify_start,
179 a.align_center,
180 a.py_md,
181 a.px_lg,
182 a.gap_xs,
183 a.rounded_sm,
184 t.atoms.bg_contrast_25,
185 gtMobile && [a.gap_sm, a.py_lg, a.mt_xs, a.px_xl],
186 (state.hovered || state.pressed) && t.atoms.bg_contrast_50,
187 ]}>
188 <desc.icon
189 size="md"
190 fill={t.atoms.text_contrast_medium.color}
191 style={{marginLeft: -2}}
192 />
193 <Text
194 style={[
195 a.flex_1,
196 a.text_left,
197 a.font_semi_bold,
198 a.leading_snug,
199 gtMobile && [a.font_semi_bold],
200 t.atoms.text_contrast_medium,
201 web({
202 marginBottom: 1,
203 }),
204 ]}
205 numberOfLines={2}>
206 {labelName}
207 </Text>
208 {!modui.noOverride && (
209 <Text
210 style={[
211 a.font_semi_bold,
212 a.leading_snug,
213 gtMobile && [a.font_semi_bold],
214 t.atoms.text_contrast_high,
215 web({
216 marginBottom: 1,
217 }),
218 ]}>
219 {override ? <Trans>Hide</Trans> : <Trans>Show</Trans>}
220 </Text>
221 )}
222 </View>
223 )}
224 </Button>
225
226 {desc.source && blur.type === 'label' && !override && (
227 <Button
228 onPress={e => {
229 e.preventDefault()
230 e.stopPropagation()
231 control.open()
232 }}
233 label={_(
234 msg`Learn more about the moderation applied to this content`,
235 )}
236 style={[a.pt_sm]}>
237 {state => (
238 <Text
239 style={[
240 a.flex_1,
241 a.text_sm,
242 a.font_normal,
243 a.leading_snug,
244 t.atoms.text_contrast_medium,
245 a.text_left,
246 ]}>
247 {desc.sourceType === 'user' ? (
248 <Trans>Labeled by the author.</Trans>
249 ) : (
250 <Trans>Labeled by {sanitizeDisplayName(desc.source!)}.</Trans>
251 )}{' '}
252 <Text
253 style={[
254 {color: t.palette.primary_500},
255 a.text_sm,
256 state.hovered && [web({textDecoration: 'underline'})],
257 ]}>
258 <Trans>Learn more.</Trans>
259 </Text>
260 </Text>
261 )}
262 </Button>
263 )}
264
265 {override && <View style={childContainerStyle}>{children}</View>}
266 </View>
267 )
268}