Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 017120b3e8bff4881d577ea0d8a2ee455e555096 268 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, 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}