Bluesky app fork with some witchin' additions 💫

Add dev mode for easy copying of at:// URIs and DIDs (#7723)

* Add dev mode for easy copying at:// URIs and DIDs

* Use storage API

* Share text instead of URL

* Cleanup persisted schema

* Change translation msg

authored by tom.sherman.is and committed by

GitHub d56efe25 db25f95c

+106 -2
+16
src/lib/sharing.ts
··· 25 25 Toast.show(t`Copied to clipboard`, 'clipboard-check') 26 26 } 27 27 } 28 + 29 + /** 30 + * This function shares a text using the native Share API if available, or copies it to the clipboard 31 + * and displays a toast message if not (mostly on web) 32 + * 33 + * @param {string} text - A string representing the text that needs to be shared or copied to the 34 + * clipboard. 35 + */ 36 + export async function shareText(text: string) { 37 + if (isAndroid || isIOS) { 38 + await Share.share({message: text}) 39 + } else { 40 + await setStringAsync(text) 41 + Toast.show(t`Copied to clipboard`, 'clipboard-check') 42 + } 43 + }
+11
src/screens/Settings/AboutSettings.tsx
··· 7 7 import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info' 8 8 import {STATUS_PAGE_URL} from '#/lib/constants' 9 9 import {CommonNavigatorParams} from '#/lib/routes/types' 10 + import {useDevModeEnabled} from '#/state/preferences/dev-mode' 10 11 import * as Toast from '#/view/com/util/Toast' 11 12 import * as SettingsList from '#/screens/Settings/components/SettingsList' 12 13 import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines' ··· 18 19 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AboutSettings'> 19 20 export function AboutSettingsScreen({}: Props) { 20 21 const {_} = useLingui() 22 + const [devModeEnabled, setDevModeEnabled] = useDevModeEnabled() 21 23 22 24 return ( 23 25 <Layout.Screen> ··· 66 68 <SettingsList.PressableItem 67 69 label={_(msg`Version ${appVersion}`)} 68 70 accessibilityHint={_(msg`Copies build version to clipboard`)} 71 + onLongPress={() => { 72 + const newDevModeEnabled = !devModeEnabled 73 + setDevModeEnabled(newDevModeEnabled) 74 + Toast.show( 75 + newDevModeEnabled 76 + ? _(msg`Developer mode enabled`) 77 + : _(msg`Developer mode disabled`), 78 + ) 79 + }} 69 80 onPress={() => { 70 81 setStringAsync( 71 82 `Build version: ${appVersion}; Bundle info: ${bundleInfo}; Bundle date: ${BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}`,
+9
src/state/preferences/dev-mode.ts
··· 1 + import {device, useStorage} from '#/storage' 2 + 3 + export function useDevModeEnabled() { 4 + const [devModeEnabled = false, setDevModeEnabled] = useStorage(device, [ 5 + 'devMode', 6 + ]) 7 + 8 + return [devModeEnabled, setDevModeEnabled] as const 9 + }
+1
src/storage/schema.ts
··· 9 9 countryCode: string | undefined 10 10 } 11 11 trendingBetaEnabled: boolean 12 + devMode: boolean 12 13 } 13 14 14 15 export type Account = {
+36 -1
src/view/com/profile/ProfileMenu.tsx
··· 6 6 7 7 import {HITSLOP_20} from '#/lib/constants' 8 8 import {makeProfileLink} from '#/lib/routes/links' 9 - import {shareUrl} from '#/lib/sharing' 9 + import {shareText, shareUrl} from '#/lib/sharing' 10 10 import {toShareUrl} from '#/lib/strings/url-helpers' 11 11 import {logger} from '#/logger' 12 12 import {Shadow} from '#/state/cache/types' 13 13 import {useModalControls} from '#/state/modals' 14 + import {useDevModeEnabled} from '#/state/preferences/dev-mode' 14 15 import { 15 16 RQKEY as profileQueryKey, 16 17 useProfileBlockMutationQueue, ··· 52 53 const isBlocked = profile.viewer?.blocking || profile.viewer?.blockedBy 53 54 const isFollowingBlockedAccount = isFollowing && isBlocked 54 55 const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked 56 + const [devModeEnabled] = useDevModeEnabled() 55 57 56 58 const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) 57 59 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) ··· 167 169 reportDialogControl.open() 168 170 }, [reportDialogControl]) 169 171 172 + const onPressShareATUri = React.useCallback(() => { 173 + shareText(`at://${profile.did}`) 174 + }, [profile.did]) 175 + 176 + const onPressShareDID = React.useCallback(() => { 177 + shareText(profile.did) 178 + }, [profile.did]) 179 + 170 180 return ( 171 181 <EventStopper onKeyDown={false}> 172 182 <Menu.Root> ··· 308 318 </Menu.Group> 309 319 </> 310 320 )} 321 + {devModeEnabled ? ( 322 + <> 323 + <Menu.Divider /> 324 + <Menu.Group> 325 + <Menu.Item 326 + testID="profileHeaderDropdownShareATURIBtn" 327 + label={_(msg`Copy at:// URI`)} 328 + onPress={onPressShareATUri}> 329 + <Menu.ItemText> 330 + <Trans>Copy at:// URI</Trans> 331 + </Menu.ItemText> 332 + <Menu.ItemIcon icon={Share} /> 333 + </Menu.Item> 334 + <Menu.Item 335 + testID="profileHeaderDropdownShareDIDBtn" 336 + label={_(msg`Copy DID`)} 337 + onPress={onPressShareDID}> 338 + <Menu.ItemText> 339 + <Trans>Copy DID</Trans> 340 + </Menu.ItemText> 341 + <Menu.ItemIcon icon={Share} /> 342 + </Menu.Item> 343 + </Menu.Group> 344 + </> 345 + ) : null} 311 346 </Menu.Outer> 312 347 </Menu.Root> 313 348
+33 -1
src/view/com/util/forms/PostDropdownBtnMenuItems.tsx
··· 21 21 import {getCurrentRoute} from '#/lib/routes/helpers' 22 22 import {makeProfileLink} from '#/lib/routes/links' 23 23 import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' 24 - import {shareUrl} from '#/lib/sharing' 24 + import {shareText, shareUrl} from '#/lib/sharing' 25 25 import {logEvent} from '#/lib/statsig/statsig' 26 26 import {richTextToString} from '#/lib/strings/rich-text-helpers' 27 27 import {toShareUrl} from '#/lib/strings/url-helpers' ··· 33 33 import {useFeedFeedbackContext} from '#/state/feed-feedback' 34 34 import {useLanguagePrefs} from '#/state/preferences' 35 35 import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' 36 + import {useDevModeEnabled} from '#/state/preferences/dev-mode' 36 37 import {usePinnedPostMutation} from '#/state/queries/pinned-post' 37 38 import { 38 39 usePostDeleteMutation, ··· 122 123 const hideReplyConfirmControl = useDialogControl() 123 124 const {mutateAsync: toggleReplyVisibility} = 124 125 useToggleReplyVisibilityMutation() 126 + const [devModeEnabled] = useDevModeEnabled() 125 127 126 128 const postUri = post.uri 127 129 const postCid = post.cid ··· 366 368 } 367 369 }, [_, queueBlock]) 368 370 371 + const onShareATURI = useCallback(() => { 372 + shareText(postUri) 373 + }, [postUri]) 374 + 375 + const onShareAuthorDID = useCallback(() => { 376 + shareText(postAuthor.did) 377 + }, [postAuthor.did]) 378 + 369 379 return ( 370 380 <> 371 381 <Menu.Outer> ··· 647 657 </> 648 658 )} 649 659 </Menu.Group> 660 + 661 + {devModeEnabled ? ( 662 + <> 663 + <Menu.Divider /> 664 + <Menu.Group> 665 + <Menu.Item 666 + testID="postAtUriShareBtn" 667 + label={_(msg`Copy post at:// URI`)} 668 + onPress={onShareATURI}> 669 + <Menu.ItemText>{_(msg`Copy post at:// URI`)}</Menu.ItemText> 670 + <Menu.ItemIcon icon={Share} position="right" /> 671 + </Menu.Item> 672 + <Menu.Item 673 + testID="postAuthorDIDShareBtn" 674 + label={_(msg`Copy author DID`)} 675 + onPress={onShareAuthorDID}> 676 + <Menu.ItemText>{_(msg`Copy author DID`)}</Menu.ItemText> 677 + <Menu.ItemIcon icon={Share} position="right" /> 678 + </Menu.Item> 679 + </Menu.Group> 680 + </> 681 + ) : null} 650 682 </> 651 683 )} 652 684 </Menu.Outer>