Bluesky app fork with some witchin' additions 馃挮
at readme-update 237 lines 6.8 kB view raw
1import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react' 2import {findNodeHandle, type ListRenderItemInfo, View} from 'react-native' 3import { 4 type AppBskyLabelerDefs, 5 type InterpretedLabelValueDefinition, 6 interpretLabelValueDefinitions, 7 type ModerationOpts, 8} from '@atproto/api' 9import {msg, Trans} from '@lingui/macro' 10import {useLingui} from '@lingui/react' 11 12import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation' 13import {List, type ListRef} from '#/view/com/util/List' 14import {atoms as a, ios, tokens, useTheme} from '#/alf' 15import {Divider} from '#/components/Divider' 16import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 17import {ListFooter} from '#/components/Lists' 18import {Loader} from '#/components/Loader' 19import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' 20import {Text} from '#/components/Typography' 21import {IS_IOS, IS_NATIVE} from '#/env' 22import {ErrorState} from '../ErrorState' 23import {type SectionRef} from './types' 24 25interface LabelsSectionProps { 26 ref: React.Ref<SectionRef> 27 isLabelerLoading: boolean 28 labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined 29 labelerError: Error | null 30 moderationOpts: ModerationOpts 31 scrollElRef: ListRef 32 headerHeight: number 33 isFocused: boolean 34 setScrollViewTag: (tag: number | null) => void 35} 36 37export function ProfileLabelsSection({ 38 ref, 39 isLabelerLoading, 40 labelerInfo, 41 labelerError, 42 moderationOpts, 43 scrollElRef, 44 headerHeight, 45 isFocused, 46 setScrollViewTag, 47}: LabelsSectionProps) { 48 const t = useTheme() 49 50 const onScrollToTop = useCallback(() => { 51 scrollElRef.current?.scrollToOffset({ 52 animated: IS_NATIVE, 53 offset: -headerHeight, 54 }) 55 }, [scrollElRef, headerHeight]) 56 57 useImperativeHandle(ref, () => ({ 58 scrollToTop: onScrollToTop, 59 })) 60 61 useEffect(() => { 62 if (IS_IOS && isFocused && scrollElRef.current) { 63 const nativeTag = findNodeHandle(scrollElRef.current) 64 setScrollViewTag(nativeTag) 65 } 66 }, [isFocused, scrollElRef, setScrollViewTag]) 67 68 const isSubscribed = labelerInfo 69 ? !!isLabelerSubscribed(labelerInfo, moderationOpts) 70 : false 71 72 const labelValues = useMemo(() => { 73 if (isLabelerLoading || !labelerInfo || labelerError) return [] 74 const customDefs = interpretLabelValueDefinitions(labelerInfo) 75 return labelerInfo.policies.labelValues 76 .filter((val, i, arr) => arr.indexOf(val) === i) // dedupe 77 .map(val => lookupLabelValueDefinition(val, customDefs)) 78 .filter( 79 def => def && def?.configurable, 80 ) as InterpretedLabelValueDefinition[] 81 }, [labelerInfo, labelerError, isLabelerLoading]) 82 83 const numItems = labelValues.length 84 85 const renderItem = useCallback( 86 ({item, index}: ListRenderItemInfo<InterpretedLabelValueDefinition>) => { 87 if (!labelerInfo) return null 88 return ( 89 <View 90 style={[ 91 t.atoms.bg_contrast_25, 92 index === 0 && [ 93 a.overflow_hidden, 94 { 95 borderTopLeftRadius: tokens.borderRadius.md, 96 borderTopRightRadius: tokens.borderRadius.md, 97 }, 98 ], 99 index === numItems - 1 && [ 100 a.overflow_hidden, 101 { 102 borderBottomLeftRadius: tokens.borderRadius.md, 103 borderBottomRightRadius: tokens.borderRadius.md, 104 }, 105 ], 106 ]}> 107 {index !== 0 && <Divider />} 108 <LabelerLabelPreference 109 disabled={isSubscribed ? undefined : true} 110 labelDefinition={item} 111 labelerDid={labelerInfo.creator.did} 112 /> 113 </View> 114 ) 115 }, 116 [labelerInfo, isSubscribed, numItems, t], 117 ) 118 119 return ( 120 <View> 121 <List 122 ref={scrollElRef} 123 data={labelValues} 124 renderItem={renderItem} 125 keyExtractor={keyExtractor} 126 contentContainerStyle={a.px_xl} 127 headerOffset={headerHeight} 128 progressViewOffset={ios(0)} 129 ListHeaderComponent={ 130 <LabelerListHeader 131 isLabelerLoading={isLabelerLoading} 132 labelerInfo={labelerInfo} 133 labelerError={labelerError} 134 hasValues={labelValues.length !== 0} 135 isSubscribed={isSubscribed} 136 /> 137 } 138 ListFooterComponent={ 139 <ListFooter 140 height={headerHeight + 180} 141 style={a.border_transparent} 142 /> 143 } 144 /> 145 </View> 146 ) 147} 148 149function keyExtractor(item: InterpretedLabelValueDefinition) { 150 return item.identifier 151} 152 153export function LabelerListHeader({ 154 isLabelerLoading, 155 labelerError, 156 labelerInfo, 157 hasValues, 158 isSubscribed, 159}: { 160 isLabelerLoading: boolean 161 labelerError?: Error | null 162 labelerInfo?: AppBskyLabelerDefs.LabelerViewDetailed 163 hasValues: boolean 164 isSubscribed: boolean 165}) { 166 const t = useTheme() 167 const {_} = useLingui() 168 169 if (isLabelerLoading) { 170 return ( 171 <View style={[a.w_full, a.align_center, a.py_4xl]}> 172 <Loader size="xl" /> 173 </View> 174 ) 175 } 176 177 if (labelerError || !labelerInfo) { 178 return ( 179 <View style={[a.w_full, a.align_center, a.py_4xl]}> 180 <ErrorState 181 error={ 182 labelerError?.toString() || 183 _(msg`Something went wrong, please try again.`) 184 } 185 /> 186 </View> 187 ) 188 } 189 190 return ( 191 <View style={[a.py_xl]}> 192 <Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_sm]}> 193 <Trans> 194 Labels are annotations on users and content. They can be used to hide, 195 warn, and categorize the network. 196 </Trans> 197 </Text> 198 {labelerInfo?.creator.viewer?.blocking ? ( 199 <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> 200 <CircleInfo size="sm" fill={t.atoms.text_contrast_medium.color} /> 201 <Text style={[t.atoms.text_contrast_high, a.leading_snug, a.text_sm]}> 202 <Trans> 203 Blocking does not prevent this labeler from placing labels on your 204 account. 205 </Trans> 206 </Text> 207 </View> 208 ) : null} 209 {!hasValues ? ( 210 <Text 211 style={[ 212 a.pt_xl, 213 t.atoms.text_contrast_high, 214 a.leading_snug, 215 a.text_sm, 216 ]}> 217 <Trans> 218 This labeler hasn't declared what labels it publishes, and may not 219 be active. 220 </Trans> 221 </Text> 222 ) : !isSubscribed ? ( 223 <Text 224 style={[ 225 a.pt_xl, 226 t.atoms.text_contrast_high, 227 a.leading_snug, 228 a.text_sm, 229 ]}> 230 <Trans> 231 Subscribe to @{labelerInfo.creator.handle} to use these labels: 232 </Trans> 233 </Text> 234 ) : null} 235 </View> 236 ) 237}