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