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 223 lines 5.9 kB view raw
1import {Keyboard, View} from 'react-native' 2import { 3 type AppBskyActorDefs, 4 type AppBskyFeedDefs, 5 moderateFeedGenerator, 6 moderateProfile, 7 type ModerationOpts, 8 type ModerationUI, 9} from '@atproto/api' 10import {msg} from '@lingui/core/macro' 11import {useLingui} from '@lingui/react' 12import {Trans} from '@lingui/react/macro' 13 14import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants' 15import {sanitizeDisplayName} from '#/lib/strings/display-names' 16import {sanitizeHandle} from '#/lib/strings/handles' 17import {useSession} from '#/state/session' 18import {UserAvatar} from '#/view/com/util/UserAvatar' 19import { 20 type WizardAction, 21 type WizardState, 22} from '#/screens/StarterPack/Wizard/State' 23import {atoms as a, useTheme} from '#/alf' 24import {Button, ButtonText} from '#/components/Button' 25import * as Toggle from '#/components/forms/Toggle' 26import {Checkbox} from '#/components/forms/Toggle' 27import {Text} from '#/components/Typography' 28import {useAnalytics} from '#/analytics' 29import type * as bsky from '#/types/bsky' 30 31function WizardListCard({ 32 type, 33 btnType, 34 displayName, 35 subtitle, 36 onPress, 37 avatar, 38 included, 39 disabled, 40 moderationUi, 41}: { 42 type: 'user' | 'algo' 43 btnType: 'checkbox' | 'remove' 44 profile?: AppBskyActorDefs.ProfileViewBasic 45 feed?: AppBskyFeedDefs.GeneratorView 46 displayName: string 47 subtitle: string 48 onPress: () => void 49 avatar?: string 50 included?: boolean 51 disabled?: boolean 52 moderationUi: ModerationUI 53}) { 54 const t = useTheme() 55 const {_} = useLingui() 56 57 return ( 58 <Toggle.Item 59 name={type === 'user' ? _(msg`Person toggle`) : _(msg`Feed toggle`)} 60 label={ 61 included 62 ? _(msg`Remove ${displayName} from starter pack`) 63 : _(msg`Add ${displayName} to starter pack`) 64 } 65 value={included} 66 disabled={btnType === 'remove' || disabled} 67 onChange={onPress} 68 style={[ 69 a.flex_row, 70 a.align_center, 71 a.px_lg, 72 a.py_md, 73 a.gap_md, 74 a.border_b, 75 t.atoms.border_contrast_low, 76 ]}> 77 <UserAvatar 78 size={45} 79 avatar={avatar} 80 moderation={moderationUi} 81 type={type} 82 /> 83 <View style={[a.flex_1, a.gap_2xs]}> 84 <Text 85 emoji 86 style={[ 87 a.flex_1, 88 a.font_semi_bold, 89 a.text_md, 90 a.leading_tight, 91 a.self_start, 92 ]} 93 numberOfLines={1}> 94 {displayName} 95 </Text> 96 <Text 97 style={[a.flex_1, a.leading_tight, t.atoms.text_contrast_medium]} 98 numberOfLines={1}> 99 {subtitle} 100 </Text> 101 </View> 102 {btnType === 'checkbox' ? ( 103 <Checkbox /> 104 ) : !disabled ? ( 105 <Button 106 label={_(msg`Remove`)} 107 variant="solid" 108 color="secondary" 109 size="small" 110 style={[a.self_center, {marginLeft: 'auto'}]} 111 onPress={onPress}> 112 <ButtonText> 113 <Trans>Remove</Trans> 114 </ButtonText> 115 </Button> 116 ) : null} 117 </Toggle.Item> 118 ) 119} 120 121export function WizardProfileCard({ 122 btnType, 123 state, 124 dispatch, 125 profile, 126 moderationOpts, 127}: { 128 btnType: 'checkbox' | 'remove' 129 state: WizardState 130 dispatch: (action: WizardAction) => void 131 profile: bsky.profile.AnyProfileView 132 moderationOpts: ModerationOpts 133}) { 134 const ax = useAnalytics() 135 const {currentAccount} = useSession() 136 137 // Determine the "main" profile for this starter pack - either targetDid or current account 138 const targetProfileDid = state.targetDid || currentAccount?.did 139 const isTarget = profile.did === targetProfileDid 140 const included = isTarget || state.profiles.some(p => p.did === profile.did) 141 const disabled = 142 isTarget || 143 (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE - 1) 144 const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar') 145 const displayName = profile.displayName 146 ? sanitizeDisplayName(profile.displayName) 147 : `@${sanitizeHandle(profile.handle)}` 148 149 const onPress = () => { 150 if (disabled) return 151 152 Keyboard.dismiss() 153 if (profile.did === targetProfileDid) return 154 155 if (!included) { 156 ax.metric('starterPack:addUser', {}) 157 dispatch({type: 'AddProfile', profile}) 158 } else { 159 ax.metric('starterPack:removeUser', {}) 160 dispatch({type: 'RemoveProfile', profileDid: profile.did}) 161 } 162 } 163 164 return ( 165 <WizardListCard 166 type="user" 167 btnType={btnType} 168 displayName={displayName} 169 subtitle={`@${sanitizeHandle(profile.handle)}`} 170 onPress={onPress} 171 avatar={profile.avatar} 172 included={included} 173 disabled={disabled} 174 moderationUi={moderationUi} 175 /> 176 ) 177} 178 179export function WizardFeedCard({ 180 btnType, 181 generator, 182 state, 183 dispatch, 184 moderationOpts, 185}: { 186 btnType: 'checkbox' | 'remove' 187 generator: AppBskyFeedDefs.GeneratorView 188 state: WizardState 189 dispatch: (action: WizardAction) => void 190 moderationOpts: ModerationOpts 191}) { 192 const isDiscover = generator.uri === DISCOVER_FEED_URI 193 const included = isDiscover || state.feeds.some(f => f.uri === generator.uri) 194 const disabled = isDiscover || (!included && state.feeds.length >= 3) 195 const moderationUi = moderateFeedGenerator(generator, moderationOpts).ui( 196 'avatar', 197 ) 198 199 const onPress = () => { 200 if (disabled) return 201 202 Keyboard.dismiss() 203 if (included) { 204 dispatch({type: 'RemoveFeed', feedUri: generator.uri}) 205 } else { 206 dispatch({type: 'AddFeed', feed: generator}) 207 } 208 } 209 210 return ( 211 <WizardListCard 212 type="algo" 213 btnType={btnType} 214 displayName={sanitizeDisplayName(generator.displayName)} 215 subtitle={`Feed by @${sanitizeHandle(generator.creator.handle)}`} 216 onPress={onPress} 217 avatar={generator.avatar} 218 included={included} 219 disabled={disabled} 220 moderationUi={moderationUi} 221 /> 222 ) 223}