Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Couple of starter packs tweaks (#4604)

authored by

Hailey and committed by
GitHub
77a512ae f769564e

+57 -63
+1 -3
app.config.js
··· 45 45 'appclips:bsky.app', 46 46 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes 47 47 // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port. 48 - ...(IS_DEV || IS_TESTFLIGHT 49 - ? ['appclips:sptesting.haileyok.com', 'applinks:sptesting.haileyok.com'] 50 - : []), 48 + ...(IS_DEV || IS_TESTFLIGHT ? [] : []), 51 49 ] 52 50 53 51 const UPDATES_CHANNEL = IS_TESTFLIGHT
+2 -8
src/components/StarterPack/QrCodeDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import ViewShot from 'react-native-view-shot' 4 - import * as FS from 'expo-file-system' 5 4 import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' 5 + import {createAssetAsync} from 'expo-media-library' 6 6 import * as Sharing from 'expo-sharing' 7 7 import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 - import {nanoid} from 'nanoid/non-secure' 11 10 12 11 import {logger} from '#/logger' 13 - import {saveImageToMediaLibrary} from 'lib/media/manip' 14 12 import {logEvent} from 'lib/statsig/statsig' 15 13 import {isNative, isWeb} from 'platform/detection' 16 14 import * as Toast from '#/view/com/util/Toast' ··· 65 63 return 66 64 } 67 65 68 - const filename = `${FS.documentDirectory}/${nanoid(12)}.png` 69 - 70 66 // Incase of a FS failure, don't crash the app 71 67 try { 72 - await FS.copyAsync({from: uri, to: filename}) 73 - await saveImageToMediaLibrary({uri: filename}) 74 - await FS.deleteAsync(filename) 68 + await createAssetAsync(`file://${uri}`) 75 69 } catch (e: unknown) { 76 70 Toast.show(_(msg`An error occurred while saving the QR code!`)) 77 71 logger.error('Failed to save QR code', {error: e})
+5 -18
src/components/StarterPack/ShareDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import * as FS from 'expo-file-system' 4 3 import {Image} from 'expo-image' 5 4 import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' 6 5 import {AppBskyGraphDefs} from '@atproto/api' 7 6 import {msg, Trans} from '@lingui/macro' 8 7 import {useLingui} from '@lingui/react' 9 - import {nanoid} from 'nanoid/non-secure' 10 8 11 9 import {logger} from '#/logger' 12 10 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' ··· 72 70 return 73 71 } 74 72 75 - const cachePath = await Image.getCachePathAsync(imageUrl) 76 - const filename = `${FS.documentDirectory}/${nanoid(12)}.png` 77 - 78 - if (!cachePath) { 79 - Toast.show(_(msg`An error occurred while saving the image.`)) 80 - return 81 - } 82 - 83 73 try { 84 - await FS.copyAsync({from: cachePath, to: filename}) 85 - await saveImageToMediaLibrary({uri: filename}) 86 - await FS.deleteAsync(filename) 87 - 74 + await saveImageToMediaLibrary({uri: imageUrl}) 88 75 Toast.show(_(msg`Image saved to your camera roll!`)) 89 76 control.close() 90 77 } catch (e: unknown) { ··· 133 120 isWeb && [a.gap_sm, a.flex_row_reverse, {marginLeft: 'auto'}], 134 121 ]}> 135 122 <Button 136 - label="Share link" 123 + label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)} 137 124 variant="solid" 138 125 color="secondary" 139 126 size="small" 140 127 style={[isWeb && a.self_center]} 141 128 onPress={onShareLink}> 142 129 <ButtonText> 143 - {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share Link</Trans>} 130 + {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>} 144 131 </ButtonText> 145 132 </Button> 146 133 <Button 147 - label="Create QR code" 134 + label={_(msg`Share QR code`)} 148 135 variant="solid" 149 136 color="secondary" 150 137 size="small" ··· 155 142 }) 156 143 }}> 157 144 <ButtonText> 158 - <Trans>Create QR code</Trans> 145 + <Trans>Share QR code</Trans> 159 146 </ButtonText> 160 147 </Button> 161 148 {isNative && (
+2
src/components/StarterPack/Wizard/WizardEditListDialog.tsx
··· 58 58 state.currentStep === 'Profiles' ? ( 59 59 <WizardProfileCard 60 60 profile={item} 61 + btnType="remove" 61 62 state={state} 62 63 dispatch={dispatch} 63 64 moderationOpts={moderationOpts} ··· 65 66 ) : ( 66 67 <WizardFeedCard 67 68 generator={item} 69 + btnType="remove" 68 70 state={state} 69 71 dispatch={dispatch} 70 72 moderationOpts={moderationOpts}
+26 -3
src/components/StarterPack/Wizard/WizardListCard.tsx
··· 9 9 ModerationUI, 10 10 } from '@atproto/api' 11 11 import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' 12 - import {msg} from '@lingui/macro' 12 + import {msg, Trans} from '@lingui/macro' 13 13 import {useLingui} from '@lingui/react' 14 14 15 15 import {DISCOVER_FEED_URI} from 'lib/constants' ··· 19 19 import {UserAvatar} from 'view/com/util/UserAvatar' 20 20 import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' 21 21 import {atoms as a, useTheme} from '#/alf' 22 + import {Button, ButtonText} from '#/components/Button' 22 23 import * as Toggle from '#/components/forms/Toggle' 23 24 import {Checkbox} from '#/components/forms/Toggle' 24 25 import {Text} from '#/components/Typography' 25 26 26 27 function WizardListCard({ 27 28 type, 29 + btnType, 28 30 displayName, 29 31 subtitle, 30 32 onPress, ··· 34 36 moderationUi, 35 37 }: { 36 38 type: 'user' | 'algo' 39 + btnType: 'checkbox' | 'remove' 37 40 profile?: AppBskyActorDefs.ProfileViewBasic 38 41 feed?: AppBskyFeedDefs.GeneratorView 39 42 displayName: string ··· 56 59 : _(msg`Add ${displayName} to starter pack`) 57 60 } 58 61 value={included} 59 - disabled={disabled} 62 + disabled={btnType === 'remove' || disabled} 60 63 onChange={onPress} 61 64 style={[ 62 65 a.flex_row, ··· 85 88 {subtitle} 86 89 </Text> 87 90 </View> 88 - <Checkbox /> 91 + {btnType === 'checkbox' ? ( 92 + <Checkbox /> 93 + ) : !disabled ? ( 94 + <Button 95 + label={_(msg`Remove`)} 96 + variant="solid" 97 + color="secondary" 98 + size="xsmall" 99 + style={[a.self_center, {marginLeft: 'auto'}]} 100 + onPress={onPress}> 101 + <ButtonText> 102 + <Trans>Remove</Trans> 103 + </ButtonText> 104 + </Button> 105 + ) : null} 89 106 </Toggle.Item> 90 107 ) 91 108 } 92 109 93 110 export function WizardProfileCard({ 111 + btnType, 94 112 state, 95 113 dispatch, 96 114 profile, 97 115 moderationOpts, 98 116 }: { 117 + btnType: 'checkbox' | 'remove' 99 118 state: WizardState 100 119 dispatch: (action: WizardAction) => void 101 120 profile: AppBskyActorDefs.ProfileViewBasic ··· 127 146 return ( 128 147 <WizardListCard 129 148 type="user" 149 + btnType={btnType} 130 150 displayName={displayName} 131 151 subtitle={`@${sanitizeHandle(profile.handle)}`} 132 152 onPress={onPress} ··· 139 159 } 140 160 141 161 export function WizardFeedCard({ 162 + btnType, 142 163 generator, 143 164 state, 144 165 dispatch, 145 166 moderationOpts, 146 167 }: { 168 + btnType: 'checkbox' | 'remove' 147 169 generator: GeneratorView 148 170 state: WizardState 149 171 dispatch: (action: WizardAction) => void ··· 170 192 return ( 171 193 <WizardListCard 172 194 type="algo" 195 + btnType={btnType} 173 196 displayName={sanitizeDisplayName(generator.displayName)} 174 197 subtitle={`Feed by @${sanitizeHandle(generator.creator.handle)}`} 175 198 onPress={onPress}
+8 -5
src/screens/StarterPack/Wizard/StepFeeds.tsx
··· 40 40 const {data: popularFeedsPages, fetchNextPage} = useGetPopularFeedsQuery({ 41 41 limit: 30, 42 42 }) 43 - const popularFeeds = 44 - popularFeedsPages?.pages 45 - .flatMap(page => page.feeds) 46 - .filter(f => !savedFeeds?.some(sf => sf?.uri === f.uri)) ?? [] 43 + const popularFeeds = popularFeedsPages?.pages.flatMap(p => p.feeds) ?? [] 47 44 48 - const suggestedFeeds = savedFeeds?.concat(popularFeeds) 45 + const suggestedFeeds = 46 + savedFeeds.length === 0 47 + ? popularFeeds 48 + : savedFeeds.concat( 49 + popularFeeds.filter(f => !savedFeeds.some(sf => sf.uri === f.uri)), 50 + ) 49 51 50 52 const {data: searchedFeeds, isLoading: isLoadingSearch} = 51 53 useSearchPopularFeedsQuery({q: throttledQuery}) ··· 56 58 return ( 57 59 <WizardFeedCard 58 60 generator={item} 61 + btnType="checkbox" 59 62 state={state} 60 63 dispatch={dispatch} 61 64 moderationOpts={moderationOpts}
+1
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 45 45 return ( 46 46 <WizardProfileCard 47 47 profile={item} 48 + btnType="checkbox" 48 49 state={state} 49 50 dispatch={dispatch} 50 51 moderationOpts={moderationOpts}
+12 -26
src/screens/StarterPack/Wizard/index.tsx
··· 21 21 22 22 import {logger} from '#/logger' 23 23 import {HITSLOP_10} from 'lib/constants' 24 + import {createSanitizedDisplayName} from 'lib/moderation/create-sanitized-display-name' 24 25 import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' 25 26 import {logEvent} from 'lib/statsig/statsig' 26 27 import {sanitizeDisplayName} from 'lib/strings/display-names' ··· 170 171 ) 171 172 172 173 const getDefaultName = () => { 173 - let displayName 174 - if ( 175 - currentProfile?.displayName != null && 176 - currentProfile?.displayName !== '' 177 - ) { 178 - displayName = sanitizeDisplayName(currentProfile.displayName) 179 - } else { 180 - displayName = sanitizeHandle(currentProfile!.handle) 181 - } 174 + const displayName = createSanitizedDisplayName(currentProfile!, true) 182 175 return _(msg`${displayName}'s Starter Pack`).slice(0, 50) 183 176 } 184 177 ··· 191 184 nextBtn: _(msg`Next`), 192 185 }, 193 186 Profiles: { 194 - header: _(msg`People`), 187 + header: _(msg`Choose People`), 195 188 nextBtn: _(msg`Next`), 196 - subtitle: _( 197 - msg`Add people to your starter pack that you think others will enjoy following`, 198 - ), 199 189 }, 200 190 Feeds: { 201 - header: _(msg`Feeds`), 191 + header: _(msg`Choose Feeds`), 202 192 nextBtn: state.feeds.length === 0 ? _(msg`Skip`) : _(msg`Finish`), 203 - subtitle: _(msg`Some subtitle`), 204 193 }, 205 194 } 206 195 const currUiStrings = wizardUiStrings[state.currentStep] ··· 254 243 dispatch({type: 'SetProcessing', processing: true}) 255 244 if (currentStarterPack && currentListItems) { 256 245 editStarterPack({ 257 - name: state.name ?? getDefaultName(), 258 - description: state.description, 246 + name: state.name?.trim() || getDefaultName(), 247 + description: state.description?.trim(), 259 248 descriptionFacets: [], 260 249 profiles: state.profiles, 261 250 feeds: state.feeds, ··· 264 253 }) 265 254 } else { 266 255 createStarterPack({ 267 - name: state.name ?? getDefaultName(), 268 - description: state.description, 256 + name: state.name?.trim() || getDefaultName(), 257 + description: state.description?.trim(), 269 258 descriptionFacets: [], 270 259 profiles: state.profiles, 271 260 feeds: state.feeds, ··· 483 472 </Trans> 484 473 ) : items.length === 2 ? ( 485 474 <Trans> 475 + <Text style={[a.font_bold, textStyles]}>You</Text> and 476 + <Text> </Text> 486 477 <Text style={[a.font_bold, textStyles]}> 487 478 {getName(items[initialNamesIndex])}{' '} 488 - </Text> 489 - and 490 - <Text> </Text> 491 - <Text style={[a.font_bold, textStyles]}> 492 - {getName(items[state.currentStep === 'Profiles' ? 0 : 1])}{' '} 493 479 </Text> 494 480 are included in your starter pack 495 481 </Trans> ··· 579 565 580 566 function getName(item: AppBskyActorDefs.ProfileViewBasic | GeneratorView) { 581 567 if (typeof item.displayName === 'string') { 582 - return enforceLen(sanitizeDisplayName(item.displayName), 16, true) 568 + return enforceLen(sanitizeDisplayName(item.displayName), 28, true) 583 569 } else if (typeof item.handle === 'string') { 584 - return enforceLen(sanitizeHandle(item.handle), 16, true) 570 + return enforceLen(sanitizeHandle(item.handle), 28, true) 585 571 } 586 572 return '' 587 573 }