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