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

Configure Feed

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

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