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