diff --git a/README.md b/README.md index d6376fa88..9a6ff7b62 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ alt="Get it on Obtainium" height="54" /> --> - You can change "post" to any phrase you want in the UI - Choose between sharing witchsky.app or bsky.app links - Embed player works with [stream.place](https://stream.place/) links! -- Open posts in PDSls, Skythread, and original bridged fedi instances +- Open posts in a configurable ATProto explorer, Skythread, and original bridged fedi instances - You can delete and redraft (basically edit!) posts - Better defaults (alt text required 😉 autoplay off 🫨) - Read your chat threads with deleted/deactivated accounts @@ -42,10 +42,7 @@ alt="Get it on Obtainium" height="54" /> --> These are all available as options in the Runes sub-page of the app's settings. - Menus - - Use handles in profile links instead of DIDs - - Show the Open submenu for external post and profile links - - On non-bsky.social handles, show a link to that URL - - Only show URL on handles with working links + - Configure the ATProto explorer name and link used by Open menus - Badges - Trusted verifiers - Use own selection of trusted verifiers @@ -68,8 +65,14 @@ These are all available as options in the Runes sub-page of the app's settings. - Disable top-of-feed button - Show plus icon on unfollowed feed avatars - Hide posts that cannot be replied to from feeds + - Profiles + - Use handles in profile links instead of DIDs + - On non-bsky.social handles, show a link to that URL + - Only show URL on handles with working links + - Show Germ DM button on profiles + - Show standard profile UI on labeler accounts + - Disable similar accounts recommendations - Redirect through go.bsky.app - - Disable similar accounts recommendations - Hide follow button on posts and scrolled profile header - Confirm before following or unfollowing - Disable verify email reminder diff --git a/src/Navigation.tsx b/src/Navigation.tsx index d191b6858..c70b4d7a9 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -132,6 +132,7 @@ import {RunesUsabilityFeedSettingsScreen} from '#/screens/Settings/RunesSettings import {RunesImpressionsSettingsScreen} from '#/screens/Settings/RunesSettings/ImpressionsSettings' import {RunesInfrastructureSettingsScreen} from '#/screens/Settings/RunesSettings/InfrastructureSettings' import {RunesMenusSettingsScreen} from '#/screens/Settings/RunesSettings/MenusSettings' +import {RunesUsabilityProfileSettingsScreen} from '#/screens/Settings/RunesSettings/ProfileSettings' import {RunesSettingsSyncSettingsScreen} from '#/screens/Settings/RunesSettings/SettingsSyncSettings' import {RunesUsabilitySettingsScreen} from '#/screens/Settings/RunesSettings/UsabilitySettings' import {SettingsScreen} from '#/screens/Settings/Settings' @@ -462,6 +463,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { getComponent={() => RunesUsabilityFeedSettingsScreen} options={{title: title(msg`Feeds`), requireAuth: true}} /> + RunesUsabilityProfileSettingsScreen} + options={{title: title(msg`Profiles`), requireAuth: true}} + /> RunesDisplaySettingsScreen} diff --git a/src/components/Menu/index.web.tsx b/src/components/Menu/index.web.tsx index c0ecf63df..8055532bf 100644 --- a/src/components/Menu/index.web.tsx +++ b/src/components/Menu/index.web.tsx @@ -316,6 +316,7 @@ export function Item({ export function Submenu({children, label, trigger, style}: SubmenuProps) { const t = useTheme() const {reduceMotionEnabled} = useA11y() + const [open, setOpen] = useState(false) const { state: hovered, onIn: onMouseEnter, @@ -324,12 +325,13 @@ export function Submenu({children, label, trigger, style}: SubmenuProps) { const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() return ( - + setOpen(true)} onFocus={onFocus} onBlur={onBlur} style={flatten([ diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx index b51bb705f..f638ef2af 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx @@ -14,7 +14,10 @@ import {type NavigationProp} from '#/lib/routes/types' import {shareText, shareUrl} from '#/lib/sharing' import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' import {useProfileShadow} from '#/state/cache/profile-shadow' -import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' +import { + toAtprotoExplorerUrl, + useAtprotoExplorer, +} from '#/state/preferences/atproto-explorer' import {precachePost} from '#/state/queries/post' import {useSession} from '#/state/session' import {atoms as a} from '#/alf' @@ -25,8 +28,8 @@ import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/i import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane' -import {BlueskyIcon} from '#/components/icons/providers/Bluesky' -import {PDSlsIcon} from '#/components/icons/providers/PDSls' +import {BlueskyIcon} from '#/components/icons/services/Bluesky' +import {PDSlsIcon} from '#/components/icons/services/PDSls' import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' import * as Menu from '#/components/Menu' import {CheckboxItemText} from '#/components/Menu/CheckboxItem' @@ -124,7 +127,7 @@ let ShareMenuItems = ({ void shareText(postAuthor.did) } - const showExternalShareButtons = useShowExternalShareButtons() + const atprotoExplorer = useAtprotoExplorer() const isBridgedPost = !!post.record.bridgyOriginalUrl || !!post.record.fediverseId const originalPostUrl = (post.record.bridgyOriginalUrl || @@ -136,8 +139,8 @@ let ShareMenuItems = ({ } } - const onOpenPostInPdsls = () => { - openLink(`https://pds.ls/${post.uri}`, true) + const onOpenPostInAtprotoExplorer = () => { + openLink(toAtprotoExplorerUrl(atprotoExplorer, post.uri), true) } const onOpenPostInSkythread = () => { @@ -225,50 +228,51 @@ let ShareMenuItems = ({ - {showExternalShareButtons && ( - - - Open - - - - }> - - {isBridgedPost && ( - - - Original post - - - - )} - - - PDSls - - - + + + Open + + + + }> + + {isBridgedPost && ( + testID="postDropdownOpenOriginalPost" + label={_(msg`Original post`)} + onPress={onOpenOriginalPost}> - Skythread + Original post - - - )} + )} + + {atprotoExplorer.name} + + + + + Skythread + + + + + {hideInPWI && ( diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx index f2805c82f..0e578f3f5 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx @@ -11,7 +11,10 @@ import {type NavigationProp} from '#/lib/routes/types' import {shareText, shareUrl} from '#/lib/sharing' import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' import {useProfileShadow} from '#/state/cache/profile-shadow' -import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' +import { + toAtprotoExplorerUrl, + useAtprotoExplorer, +} from '#/state/preferences/atproto-explorer' import {useSession} from '#/state/session' import {useBreakpoints} from '#/alf' import {useDialogControl} from '#/components/Dialog' @@ -22,8 +25,8 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/compon import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' -import {BlueskyIcon} from '#/components/icons/providers/Bluesky' -import {PDSlsIcon} from '#/components/icons/providers/PDSls' +import {BlueskyIcon} from '#/components/icons/services/Bluesky' +import {PDSlsIcon} from '#/components/icons/services/PDSls' import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight' import * as Menu from '#/components/Menu' import {useAgeAssurance} from '#/ageAssurance' @@ -96,7 +99,7 @@ let ShareMenuItems = ({ void shareText(postAuthor.did) } - const showExternalShareButtons = useShowExternalShareButtons() + const atprotoExplorer = useAtprotoExplorer() const isBridgedPost = !!post.record.bridgyOriginalUrl || !!post.record.fediverseId const originalPostUrl = (post.record.bridgyOriginalUrl || @@ -108,8 +111,8 @@ let ShareMenuItems = ({ } } - const onOpenPostInPdsls = () => { - openLink(`https://pds.ls/${post.uri}`, true) + const onOpenPostInAtprotoExplorer = () => { + openLink(toAtprotoExplorerUrl(atprotoExplorer, post.uri), true) } const onOpenPostInSkythread = () => { @@ -156,51 +159,49 @@ let ShareMenuItems = ({ - {showExternalShareButtons && ( - - - Open - - - - }> - {isBridgedPost && ( - - - Original post - - - - )} - + - PDSls + Open - - + + + }> + {isBridgedPost && ( + testID="postDropdownOpenOriginalPost" + label={_(msg`Original post`)} + onPress={onOpenOriginalPost}> - Skythread + Original post - - )} + )} + + {atprotoExplorer.name} + + + + + Skythread + + + + {hasSession && aa.state.access === aa.Access.Full && ( diff --git a/src/components/icons/providers/Blacksky.tsx b/src/components/icons/services/Blacksky.tsx similarity index 100% rename from src/components/icons/providers/Blacksky.tsx rename to src/components/icons/services/Blacksky.tsx diff --git a/src/components/icons/providers/Bluesky.tsx b/src/components/icons/services/Bluesky.tsx similarity index 100% rename from src/components/icons/providers/Bluesky.tsx rename to src/components/icons/services/Bluesky.tsx diff --git a/src/components/icons/services/Clearsky.tsx b/src/components/icons/services/Clearsky.tsx new file mode 100644 index 000000000..33dc4aef2 --- /dev/null +++ b/src/components/icons/services/Clearsky.tsx @@ -0,0 +1,22 @@ +import {createMultiPathSVG} from '#/components/icons/TEMPLATE' + +export const ClearskyIcon = createMultiPathSVG({ + paths: [ + 'M94.9334 3.19999C81.3334 6.93332 81.7334 6.53332 85.3334 14.9333C86.9334 18.8 91.2001 29.2 94.9334 38C98.5334 46.8 101.733 54.1333 101.867 54.4C102.667 55.2 116 51.4667 116 50.5333C116 49.2 109.6 8.79998 108.533 3.59998C108.133 1.59998 107.333 -1.41189e-05 106.933 0.133319C106.4 0.133319 101.067 1.59998 94.9334 3.19999Z', + 'M165.333 7.73334C159.333 28.9333 153.467 52.5333 154.133 53.2C154.533 53.6 157.333 54.8 160.4 56L166.133 58.1333L178.4 35.6C185.2 23.0667 190.267 12.4 189.733 11.8667C189.067 11.2 183.6 8.93334 177.6 6.66667L166.8 2.66667L165.333 7.73334Z', + 'M66.8001 14.6667C51.6001 22.6667 51.7334 22.2667 60.2667 32.9333C64.2667 37.8667 70.9334 46.6667 75.2001 52.2667C84.6667 65.0667 84.1334 64.8 90.5334 60.6667C93.6001 58.8 96.5334 57.3333 97.3334 57.3333C99.0667 57.3333 99.6001 58.8 91.4667 39.7333C87.4667 30.4 83.2001 19.7333 82.0001 16.1333C79.3334 8 79.3334 8 66.8001 14.6667Z', + 'M188.267 40.8C180.8 52.9333 174.933 63.3333 175.333 64.1333C177.067 66.8 180 65.0667 185.6 58.2667C188.8 54.5333 196.133 45.7333 201.867 38.8L212.4 26.2667L207.867 22.5333C205.333 20.4 203.067 18.6667 202.667 18.6667C202.267 18.6667 195.867 28.6667 188.267 40.8Z', + 'M211.333 61.4667C190.667 79.2 191.2 78.5333 193.733 81.8667C196 84.9333 195.333 85.2 219.733 69.2C229.733 62.6667 238.667 57.0667 239.333 56.8C240.667 56.4 233.467 46.4 231.2 45.7333C230.533 45.6 221.6 52.6667 211.333 61.4667Z', + 'M18.8001 60.6667C8.66673 78.2667 2.66673 96.4 0.666732 114.933L-0.266602 124H51.7334L52.5334 117.6C53.8667 108.133 58.2667 95.8667 62.8001 88.6667C66.1334 83.4667 66.5334 82 65.0667 80.6667C60.2667 76.2667 26.6667 50.6667 25.6001 50.6667C25.0667 50.6667 21.8667 55.2 18.8001 60.6667Z', + 'M113.333 59.3333C58.6667 74.5333 40.9334 140.933 81.2001 179.6C104.4 202 142.133 205.6 168.8 188.133C176.4 183.2 190.4 170 189.2 168.8C188.933 168.533 182.8 164.667 175.733 160.133L162.8 151.867L158.267 156.267C148.133 166.533 126.533 169.733 114 163.067C78.5334 144 91.2001 90.2667 131.2 90C142.133 90 153.467 94.5333 159.2 101.467C163.333 106.267 162.133 106.533 173.6 98.8C178.933 95.3333 184.667 91.7333 186.4 90.8C190.533 88.6667 190.133 87.4667 183.467 80.2667C166.667 62.2667 135.867 53.0667 113.333 59.3333Z', + 'M26.0001 141.6C-1.19993 144.533 0.266732 144.133 1.06673 149.2C1.7334 154.4 0.800066 154.4 29.3334 148.267C47.7334 144.267 52.8001 142.8 53.0667 140.933C53.6001 138.267 55.6001 138.267 26.0001 141.6Z', + 'M58.0001 177.067C55.0667 179.2 45.6001 185.2 37.0667 190.533C28.4001 196 21.3334 200.667 21.3334 201.2C21.4667 201.6 23.4667 204.4 25.8667 207.2L30.4001 212.4L48.1334 197.067C69.7334 178.533 70.2667 178 68.0001 175.333C65.7334 172.533 64.1334 172.8 58.0001 177.067Z', + 'M190.267 179.333L185.2 185.333L202.267 204.667C211.6 215.333 219.733 224 220.267 224C221.6 224 238.667 203.6 238.667 202C238.533 200.933 199.867 175.067 196.133 173.6C195.6 173.467 192.933 176 190.267 179.333Z', + 'M70.2667 212.8C62.4001 223.467 55.6001 232.933 55.2001 233.867C54.6667 235.2 69.4667 244.8 76.1334 247.333C78.0001 248 97.3334 205.467 97.3334 200.667C97.3334 199.333 87.7334 193.333 85.7334 193.333C85.2001 193.467 78.1334 202.133 70.2667 212.8Z', + 'M173.333 194.533C173.333 195.2 179.067 205.333 186 216.933C192.933 228.533 198.8 238.533 199.067 239.2C199.6 240.667 206.533 236 206.667 234.267C206.667 233.6 199.867 224.133 191.733 213.2C178.4 195.6 173.333 190.533 173.333 194.533Z', + 'M168 198.8C168 199.467 172 208.4 176.8 218.4C181.6 228.4 186.533 238.667 187.467 241.067C189.333 245.2 191.6 245.467 195.733 241.867C196.533 241.333 170.133 197.467 169.067 197.333C168.4 197.333 168 198 168 198.8Z', + 'M104 229.067C96.1334 256.267 96.0001 255.2 106.533 256.533C109.867 257.067 114.8 257.733 117.467 258.133L122.267 258.8L123.2 233.333C123.733 219.333 124 207.6 123.733 207.2C123.333 206.933 120.4 206.133 117.067 205.6L111.067 204.4L104 229.067Z', + 'M137.6 206.267L133.2 206.8L134 258.8L143.6 258C156.267 256.8 161.467 255.6 161.467 254C161.467 253.2 158.667 242 155.2 228.933C148.133 202.667 149.867 204.8 137.6 206.267Z', + ], + viewBox: '0 0 240 259', +}) diff --git a/src/components/icons/providers/Eurosky.tsx b/src/components/icons/services/Eurosky.tsx similarity index 100% rename from src/components/icons/providers/Eurosky.tsx rename to src/components/icons/services/Eurosky.tsx diff --git a/src/components/icons/providers/PDSls.tsx b/src/components/icons/services/PDSls.tsx similarity index 100% rename from src/components/icons/providers/PDSls.tsx rename to src/components/icons/services/PDSls.tsx diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 304764673..0048f02cf 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -71,6 +71,7 @@ export type CommonNavigatorParams = { RunesImpressionsSettings: undefined RunesUsabilitySettings: undefined RunesUsabilityFeedSettings: undefined + RunesUsabilityProfileSettings: undefined RunesDisplaySettings: undefined RunesDisplayAlsoLikedSettings: undefined RunesDisplayDensitySettings: undefined diff --git a/src/routes.ts b/src/routes.ts index 54578d285..505925b5b 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -56,6 +56,7 @@ export const router = new Router({ RunesImpressionsSettings: '/settings/runes/impressions', RunesUsabilitySettings: '/settings/runes/usability', RunesUsabilityFeedSettings: '/settings/runes/usability/feeds', + RunesUsabilityProfileSettings: '/settings/runes/usability/profiles', RunesDisplaySettings: '/settings/runes/display', RunesDisplayAlsoLikedSettings: '/settings/runes/display/also-liked', RunesDisplayDensitySettings: '/settings/runes/display/density', diff --git a/src/screens/Settings/RunesSettings/MenusSettings.tsx b/src/screens/Settings/RunesSettings/MenusSettings.tsx index f4ad521ac..3064cf1bc 100644 --- a/src/screens/Settings/RunesSettings/MenusSettings.tsx +++ b/src/screens/Settings/RunesSettings/MenusSettings.tsx @@ -1,110 +1,98 @@ +import {useCallback, useRef} from 'react' +import {View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' +import {useFocusEffect} from '@react-navigation/native' import { - useSetShowExternalShareButtons, - useShowExternalShareButtons, -} from '#/state/preferences/external-share-buttons' -import {RestartRequiredPrompt} from '#/state/preferences/restart-required-prompt' -import { - useSetShowLinkInHandle, - useShowLinkInHandle, -} from '#/state/preferences/show-link-in-handle.tsx' -import { - useSetShowLinkInHandleOnlyOnWorkingLinks, - useShowLinkInHandleOnlyOnWorkingLinks, -} from '#/state/preferences/show-link-in-handle-only-on-working-links' -import { - useHandleInLinks, - useSetHandleInLinks, -} from '#/state/preferences/use-handle-in-links' + DEFAULT_ATPROTO_EXPLORER, + useAtprotoExplorerSetting, + useSetAtprotoExplorer, +} from '#/state/preferences/atproto-explorer' import * as SettingsList from '#/screens/Settings/components/SettingsList' -import * as Dialog from '#/components/Dialog' -import * as Toggle from '#/components/forms/Toggle' -import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight' -import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At' -import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' +import {atoms as a} from '#/alf' +import * as TextField from '#/components/forms/TextField' +import {Text} from '#/components/Typography' import {RunesScreenLayout} from './components/RunesScreenLayout' export function RunesMenusSettingsScreen() { const {t: l} = useLingui() + const atprotoExplorer = useAtprotoExplorerSetting() + const setAtprotoExplorer = useSetAtprotoExplorer() + const atprotoExplorerRef = useRef(atprotoExplorer) + atprotoExplorerRef.current = atprotoExplorer - const handleInLinks = useHandleInLinks() - const setHandleInLinks = useSetHandleInLinks() - const restartPromptControl = Dialog.useDialogControl() - - const showExternalShareButtons = useShowExternalShareButtons() - const setShowExternalShareButtons = useSetShowExternalShareButtons() + const updateAtprotoExplorer = useCallback( + (next: typeof atprotoExplorer) => { + atprotoExplorerRef.current = next + setAtprotoExplorer(next) + }, + [setAtprotoExplorer], + ) - const showLinkInHandle = useShowLinkInHandle() - const setShowLinkInHandle = useSetShowLinkInHandle() - const showLinkInHandleOnlyOnWorkingLinks = - useShowLinkInHandleOnlyOnWorkingLinks() - const setShowLinkInHandleOnlyOnWorkingLinks = - useSetShowLinkInHandleOnlyOnWorkingLinks() + useFocusEffect( + useCallback(() => { + return () => { + const current = atprotoExplorerRef.current + const next = { + name: current.name.trim() || DEFAULT_ATPROTO_EXPLORER.name, + url: current.url.trim() || DEFAULT_ATPROTO_EXPLORER.url, + } + if (next.name !== current.name || next.url !== current.url) { + updateAtprotoExplorer(next) + } + } + }, [updateAtprotoExplorer]), + ) return ( - { - setHandleInLinks(value) - restartPromptControl.open() - }}> - - - - Use handles in profile links instead of DIDs - - - - - setShowExternalShareButtons(value)}> - - - - Show the Open submenu for external links - - - - - setShowLinkInHandle(value)}> - - - - On non-bsky.social handles, show a link to that URL - - - - - {showLinkInHandle && ( - setShowLinkInHandleOnlyOnWorkingLinks(value)}> - - - - Only show URL on handles with working links - - - - - )} - - + + + ATProto explorer + + + + Choose the explorer used by Open menus. Use (uri) where the post, + profile, or repository AT URI should appear. + + + + + Name + + + + updateAtprotoExplorer({...atprotoExplorer, name}) + } + autoCapitalize="words" + autoCorrect={false} + /> + + + + + Link + + + + updateAtprotoExplorer({...atprotoExplorer, url}) + } + autoCapitalize="none" + autoCorrect={false} + autoComplete="url" + keyboardType="url" + /> + + + ) } diff --git a/src/screens/Settings/RunesSettings/ProfileSettings.tsx b/src/screens/Settings/RunesSettings/ProfileSettings.tsx new file mode 100644 index 000000000..3fd9b5493 --- /dev/null +++ b/src/screens/Settings/RunesSettings/ProfileSettings.tsx @@ -0,0 +1,143 @@ +import {Trans, useLingui} from '@lingui/react/macro' + +import {useSetShowGermDmButton, useShowGermDmButton} from '#/state/preferences' +import { + useHideSimilarAccountsRecomm, + useSetHideSimilarAccountsRecomm, +} from '#/state/preferences/hide-similar-accounts-recommendations' +import {RestartRequiredPrompt} from '#/state/preferences/restart-required-prompt' +import { + useSetShowLinkInHandle, + useShowLinkInHandle, +} from '#/state/preferences/show-link-in-handle' +import { + useSetShowLinkInHandleOnlyOnWorkingLinks, + useShowLinkInHandleOnlyOnWorkingLinks, +} from '#/state/preferences/show-link-in-handle-only-on-working-links' +import { + useSetShowStandardLabelerProfile, + useShowStandardLabelerProfile, +} from '#/state/preferences/show-standard-labeler-profile' +import { + useHandleInLinks, + useSetHandleInLinks, +} from '#/state/preferences/use-handle-in-links' +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import * as Dialog from '#/components/Dialog' +import * as Toggle from '#/components/forms/Toggle' +import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At' +import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' +import {Message_Stroke2_Corner0_Rounded as MessageIcon} from '#/components/icons/Message' +import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person' +import {Shield_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' +import {RunesScreenLayout} from './components/RunesScreenLayout' + +export function RunesUsabilityProfileSettingsScreen() { + const {t: l} = useLingui() + const restartPromptControl = Dialog.useDialogControl() + + const handleInLinks = useHandleInLinks() + const setHandleInLinks = useSetHandleInLinks() + const showLinkInHandle = useShowLinkInHandle() + const setShowLinkInHandle = useSetShowLinkInHandle() + const showLinkInHandleOnlyOnWorkingLinks = + useShowLinkInHandleOnlyOnWorkingLinks() + const setShowLinkInHandleOnlyOnWorkingLinks = + useSetShowLinkInHandleOnlyOnWorkingLinks() + const showGermDmButton = useShowGermDmButton() + const setShowGermDmButton = useSetShowGermDmButton() + const showStandardLabelerProfile = useShowStandardLabelerProfile() + const setShowStandardLabelerProfile = useSetShowStandardLabelerProfile() + const hideSimilarAccountsRecomm = useHideSimilarAccountsRecomm() + const setHideSimilarAccountsRecomm = useSetHideSimilarAccountsRecomm() + + return ( + + { + setHandleInLinks(value) + restartPromptControl.open() + }}> + + + + Use handles in profile links instead of DIDs + + + + + setShowLinkInHandle(value)}> + + + + On non-bsky.social handles, show a link to that URL + + + + + {showLinkInHandle && ( + setShowLinkInHandleOnlyOnWorkingLinks(value)}> + + + + Only show URL on handles with working links + + + + + )} + setShowGermDmButton(value)}> + + + + Show Germ DM button on profiles + + + + + setShowStandardLabelerProfile(value)}> + + + + Show standard profile UI on labeler accounts + + + + + setHideSimilarAccountsRecomm(value)}> + + + + Disable similar accounts recommendations + + + + + + + + ) +} diff --git a/src/screens/Settings/RunesSettings/UsabilitySettings.tsx b/src/screens/Settings/RunesSettings/UsabilitySettings.tsx index e09cbf8a2..5d1e38842 100644 --- a/src/screens/Settings/RunesSettings/UsabilitySettings.tsx +++ b/src/screens/Settings/RunesSettings/UsabilitySettings.tsx @@ -1,11 +1,6 @@ import {Trans, useLingui} from '@lingui/react/macro' -import { - useGoLinksEnabled, - useSetGoLinksEnabled, - useSetShowGermDmButton, - useShowGermDmButton, -} from '#/state/preferences' +import {useGoLinksEnabled, useSetGoLinksEnabled} from '#/state/preferences' import { useConfirmFollowUnfollow, useSetConfirmFollowUnfollow, @@ -18,14 +13,6 @@ import { useHideScaryFollowButtons, useSetHideScaryFollowButtons, } from '#/state/preferences/hide-scary-follow-buttons' -import { - useHideSimilarAccountsRecomm, - useSetHideSimilarAccountsRecomm, -} from '#/state/preferences/hide-similar-accounts-recommendations' -import { - useSetShowStandardLabelerProfile, - useShowStandardLabelerProfile, -} from '#/state/preferences/show-standard-labeler-profile' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {atoms as a} from '#/alf' import {Admonition} from '#/components/Admonition' @@ -33,11 +20,9 @@ import * as Toggle from '#/components/forms/Toggle' import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope' -import {Message_Stroke2_Corner0_Rounded as MessageIcon} from '#/components/icons/Message' import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/icons/Newspaper' import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' -import {Shield_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' import {RunesScreenLayout} from './components/RunesScreenLayout' export function RunesUsabilitySettingsScreen() { @@ -46,18 +31,9 @@ export function RunesUsabilitySettingsScreen() { const goLinksEnabled = useGoLinksEnabled() const setGoLinksEnabled = useSetGoLinksEnabled() - const hideSimilarAccountsRecomm = useHideSimilarAccountsRecomm() - const setHideSimilarAccountsRecomm = useSetHideSimilarAccountsRecomm() - const hideScaryFollowButtons = useHideScaryFollowButtons() const setHideScaryFollowButtons = useSetHideScaryFollowButtons() - const showGermDmButton = useShowGermDmButton() - const setShowGermDmButton = useSetShowGermDmButton() - - const showStandardLabelerProfile = useShowStandardLabelerProfile() - const setShowStandardLabelerProfile = useSetShowStandardLabelerProfile() - const confirmFollowUnfollow = useConfirmFollowUnfollow() const setConfirmFollowUnfollow = useSetConfirmFollowUnfollow() @@ -74,6 +50,14 @@ export function RunesUsabilitySettingsScreen() { Feeds + + + + Profiles + + - setHideSimilarAccountsRecomm(value)}> - - - - Disable similar accounts recommendations - - - - - setShowGermDmButton(value)}> - - - - Show Germ DM button on profiles - - - - - setShowStandardLabelerProfile(value)}> - - - - Show standard profile UI on labeler accounts - - - - +type SetContext = (value: StateContext) => void + +const fallback = persisted.defaults.atprotoExplorer! +export const DEFAULT_ATPROTO_EXPLORER = fallback +const stateContext = createContext(fallback) +const setContext = createContext((_: StateContext) => {}) + +function normalizeSetting(value: StateContext): StateContext { + const url = value.url.split('{uri}').join('(uri)') + return url === value.url ? value : {...value, url} +} + +export function Provider({children}: PropsWithChildren<{}>) { + const [state, setState] = useState(() => + normalizeSetting(persisted.get('atprotoExplorer') ?? fallback), + ) + + const setStateWrapped = useCallback((value: StateContext) => { + const next = normalizeSetting(value) + setState(next) + void persisted.write('atprotoExplorer', next) + }, []) + + useEffect(() => { + return persisted.onUpdate('atprotoExplorer', next => { + setState(normalizeSetting(next ?? fallback)) + }) + }, []) + + return ( + + + {children} + + + ) +} + +export function useAtprotoExplorerSetting() { + return useContext(stateContext) +} + +export function useAtprotoExplorer() { + const setting = useContext(stateContext) + return { + name: setting.name.trim() || fallback.name, + url: setting.url.trim() || fallback.url, + } +} + +export function useSetAtprotoExplorer() { + return useContext(setContext) +} + +export function toAtprotoExplorerUrl( + explorer: ReturnType, + uri: string, +) { + const template = explorer.url.includes('(uri)') + ? explorer.url.split('(uri)').join(uri) + : `${explorer.url.replace(/\/+$/, '')}/${uri}` + + try { + const url = new URL(template) + if (url.protocol === 'http:' || url.protocol === 'https:') { + return url.toString() + } + } catch {} + + return fallback.url.split('(uri)').join(uri) +} diff --git a/src/state/preferences/external-share-buttons.tsx b/src/state/preferences/external-share-buttons.tsx deleted file mode 100644 index 7b53398da..000000000 --- a/src/state/preferences/external-share-buttons.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { - createContext, - type PropsWithChildren, - useCallback, - useContext, - useEffect, - useState, -} from 'react' - -import * as persisted from '#/state/persisted' - -type StateContext = persisted.Schema['showExternalShareButtons'] -type SetContext = (v: persisted.Schema['showExternalShareButtons']) => void - -const stateContext = createContext( - persisted.defaults.showExternalShareButtons, -) -const setContext = createContext( - (_: persisted.Schema['showExternalShareButtons']) => {}, -) - -export function Provider({children}: PropsWithChildren<{}>) { - const [state, setState] = useState(persisted.get('showExternalShareButtons')) - - const setStateWrapped = useCallback( - ( - showExternalShareButtons: persisted.Schema['showExternalShareButtons'], - ) => { - setState(showExternalShareButtons) - persisted.write('showExternalShareButtons', showExternalShareButtons) - }, - [setState], - ) - - useEffect(() => { - return persisted.onUpdate( - 'showExternalShareButtons', - nextShowExternalShareButtons => { - setState(nextShowExternalShareButtons) - }, - ) - }, [setStateWrapped]) - - return ( - - - {children} - - - ) -} - -export function useShowExternalShareButtons() { - return useContext(stateContext) -} - -export function useSetShowExternalShareButtons() { - return useContext(setContext) -} diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index dba273ce8..c229cea5c 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -3,6 +3,7 @@ import {type PropsWithChildren} from 'react' import {Provider as AlsoLikedCollapseByDefaultProvider} from './also-liked-collapse-by-default' import {Provider as AlsoLikedFeedProvider} from './also-liked-feed-enabled' import {Provider as AltTextRequiredProvider} from './alt-text-required' +import {Provider as AtprotoExplorerProvider} from './atproto-explorer' import {Provider as AutoCompactAccountSwitcherProvider} from './auto-compact-account-switcher' import {Provider as AutoLikeOnRepostProvider} from './auto-like-on-repost' import {Provider as AutoplayProvider} from './autoplay' @@ -23,14 +24,13 @@ import {Provider as DownloadFormatProvider} from './download-format' import {Provider as EnableSquareAvatarsProvider} from './enable-square-avatars' import {Provider as EnableSquareButtonsProvider} from './enable-square-buttons' import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' -import {Provider as ExternalShareButtonsProvider} from './external-share-buttons' import {Provider as FaviconServiceProvider} from './favicon-service' import {Provider as FullsizeFormatProvider} from './fullsize-format' import {Provider as GoLinksProvider} from './go-links-enabled' import {Provider as HiddenPostsProvider} from './hidden-posts' +import {Provider as HideDisplayNamesProvider} from './hide-display-names' import {Provider as HideFeedsPromoTabProvider} from './hide-feeds-promo-tab' import {Provider as HideScaryFollowButtonsProvider} from './hide-scary-follow-buttons.tsx' -import {Provider as HideDisplayNamesProvider} from './hide-display-names' import {Provider as HideSimilarAccountsRecommProvider} from './hide-similar-accounts-recommendations' import {Provider as HideUnreplyablePostsProvider} from './hide-unreplyable-posts' import {Provider as IgnoredAppLabelersProvider} from './ignored-app-labelers' @@ -105,6 +105,7 @@ export { export {useFaviconService, useSetFaviconService} from './favicon-service' export {useGoLinksEnabled, useSetGoLinksEnabled} from './go-links-enabled' export {useHiddenPosts, useHiddenPostsApi} from './hidden-posts' +export {useHideDisplayNames, useSetHideDisplayNames} from './hide-display-names' export { useHideFeedsPromoTab, useSetHideFeedsPromoTab, @@ -113,10 +114,6 @@ export { useHideScaryFollowButtons, useSetHideScaryFollowButtons, } from './hide-scary-follow-buttons' -export { - useHideDisplayNames, - useSetHideDisplayNames, -} from './hide-display-names' export {useImageCdnHost, useSetImageCdnHost} from './image-cdn-host' export {useLabelDefinitions} from './label-defs' export {useLanguagePrefs, useLanguagePrefsApi} from './languages' @@ -166,7 +163,7 @@ export function Provider({children}: PropsWithChildren<{}>) { - + @@ -292,7 +289,7 @@ export function Provider({children}: PropsWithChildren<{}>) { - + diff --git a/src/state/preferences/settings-sync.tsx b/src/state/preferences/settings-sync.tsx index 2a5d6242a..8adee18a4 100644 --- a/src/state/preferences/settings-sync.tsx +++ b/src/state/preferences/settings-sync.tsx @@ -97,7 +97,7 @@ export const SYNCED_PREFS_KEYS = [ 'pdsLabel', 'faviconService', 'postReplacement', - 'showExternalShareButtons', + 'atprotoExplorer', 'translationServicePreference', 'libreTranslateInstance', 'openRouterApiKey', diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 5e8e500f7..f7b128d53 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -12,6 +12,10 @@ import {shareText, shareUrl} from '#/lib/sharing' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' import {type Shadow} from '#/state/cache/types' +import { + toAtprotoExplorerUrl, + useAtprotoExplorer, +} from '#/state/preferences/atproto-explorer' import {useConfirmFollowUnfollow} from '#/state/preferences/confirm-follow-unfollow' import { useDeerVerificationEnabled, @@ -19,7 +23,6 @@ import { useSetDeerVerificationTrust, } from '#/state/preferences/deer-verification' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' -import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' import {useDeerVerificationProfileOverlay} from '#/state/queries/deer-verification' import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' import { @@ -54,8 +57,9 @@ import { PersonX_Stroke2_Corner0_Rounded as PersonX, } from '#/components/icons/Person' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' -import {BlueskyIcon} from '#/components/icons/providers/Bluesky' -import {PDSlsIcon} from '#/components/icons/providers/PDSls' +import {BlueskyIcon} from '#/components/icons/services/Bluesky' +import {ClearskyIcon} from '#/components/icons/services/Clearsky' +import {PDSlsIcon} from '#/components/icons/services/PDSls' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import {StarterPack} from '#/components/icons/StarterPack' import * as Menu from '#/components/Menu' @@ -133,7 +137,7 @@ let ProfileMenu = ({ const addToListsDialogControl = useDialogControl() const pendingShareAction = useRef<() => void>(() => {}) - const showExternalShareButtons = useShowExternalShareButtons() + const atprotoExplorer = useAtprotoExplorer() const openLink = useOpenLink() const showLoggedOutWarning = useMemo(() => { @@ -341,15 +345,22 @@ let ProfileMenu = ({ navigation.navigate('ProfileSearch', {name: profile.handle}) }, [navigation, profile.handle]) - const onOpenProfileInPdsls = () => { + const onOpenProfileInAtprotoExplorer = () => { openLink( - `https://pds.ls/at://${profile.did}/app.bsky.actor.profile/self`, + toAtprotoExplorerUrl( + atprotoExplorer, + `at://${profile.did}/app.bsky.actor.profile/self`, + ), true, ) } - const onOpenRepoInPdsls = () => { - openLink(`https://pds.ls/at://${profile.did}`, true) + const onOpenRepoInAtprotoExplorer = () => { + openLink(toAtprotoExplorerUrl(atprotoExplorer, `at://${profile.did}`), true) + } + + const onOpenProfileInClearsky = () => { + openLink(`https://clearsky.app/${profile.did}/profile`, true) } const verificationCreatePromptControl = Prompt.usePromptControl() @@ -458,51 +469,72 @@ let ProfileMenu = ({ {!IS_WEB && ( - { copyLinksRef.current = value }}> - Copy + Copy )} - {showExternalShareButtons && ( - - - Open - - - - }> - - - - PDSls profile - - - - - - PDSls repository - - - - - - )} + + + Open + + + + }> + + + + Repository + + + + + + Profile + + + + + + Clearsky + + + + +