diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index 27b73da70..6b9686de5 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -1,12 +1,14 @@ import {View} from 'react-native' -import {AppBskyActorDefs} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {isInvalidHandle} from '#/lib/strings/handles' import {isIOS} from '#/platform/detection' -import {Shadow} from '#/state/cache/types' +import {type Shadow} from '#/state/cache/types' +import {useShowLinkInHandle} from '#/state/preferences/show-link-in-handle.tsx' import {atoms as a, useTheme, web} from '#/alf' +import {InlineLinkText} from '#/components/Link.tsx' import {NewskieDialog} from '#/components/NewskieDialog' import {Text} from '#/components/Typography' @@ -21,6 +23,8 @@ export function ProfileHeaderHandle({ const {_} = useLingui() const invalidHandle = isInvalidHandle(profile.handle) const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy + const isBskySocialHandle = profile.handle.endsWith('.bsky.social') + const showProfileInHandle = useShowLinkInHandle() return ( - {invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`} + {invalidHandle ? ( + _(msg`⚠Invalid Handle`) + ) : showProfileInHandle && !isBskySocialHandle ? ( + + + @{profile.handle} + + + ) : ( + `@${profile.handle}` + )} ) diff --git a/src/screens/Settings/DeerSettings.tsx b/src/screens/Settings/DeerSettings.tsx index c3f9a11ed..f04cb5955 100644 --- a/src/screens/Settings/DeerSettings.tsx +++ b/src/screens/Settings/DeerSettings.tsx @@ -51,6 +51,10 @@ import { useRepostCarouselEnabled, useSetRepostCarouselEnabled, } from '#/state/preferences/repost-carousel-enabled' +import { + useSetShowLinkInHandle, + useShowLinkInHandle, +} from '#/state/preferences/show-link-in-handle.tsx' import {useProfilesQuery} from '#/state/queries/profile' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {atoms as a, useBreakpoints} from '#/alf' @@ -271,6 +275,9 @@ export function DeerSettingsScreen({}: Props) { const repostCarouselEnabled = useRepostCarouselEnabled() const setRepostCarouselEnabled = useSetRepostCarouselEnabled() + const showLinkInHandle = useShowLinkInHandle() + const setShowLinkInHandle = useSetShowLinkInHandle() + const [gates, setGatesView] = useState(Object.fromEntries(useGatesCache())) const dangerousSetGate = useDangerousSetGate() const setGate = (gate: Gate, value: boolean) => { @@ -499,6 +506,21 @@ export function DeerSettingsScreen({}: Props) { + setShowLinkInHandle(value)} + style={[a.w_full]}> + + + On non-bsky.social handles, show a link to that URL + + + + diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 1f00cd69a..df03cfd73 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -133,6 +133,7 @@ const schema = z.object({ repostCarouselEnabled: z.boolean().optional(), hideFollowNotifications: z.boolean().optional(), constellationInstance: z.string().optional(), + showLinkInHandle: z.boolean().optional(), deerVerification: z .object({ enabled: z.boolean(), @@ -201,6 +202,7 @@ export const defaults: Schema = { repostCarouselEnabled: false, hideFollowNotifications: false, constellationInstance: 'https://constellation.microcosm.blue/', + showLinkInHandle: false, deerVerification: { enabled: false, // https://deer.social/profile/did:plc:p2cp5gopk7mgjegy6wadk3ep/post/3lndyqyyr4k2k diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index decafd964..f7a00d62e 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -18,6 +18,7 @@ import {Provider as LargeAltBadgeProvider} from './large-alt-badge' import {Provider as NoAppLabelersProvider} from './no-app-labelers' import {Provider as NoDiscoverProvider} from './no-discover-fallback' import {Provider as RepostCarouselProvider} from './repost-carousel-enabled' +import {Provider as ShowLinkInHandleProvider} from './show-link-in-handle' import {Provider as SubtitlesProvider} from './subtitles' import {Provider as TrendingSettingsProvider} from './trending' import {Provider as UsedStarterPacksProvider} from './used-starter-packs' @@ -50,29 +51,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - - - - - - - - - - - - {children} - - - - - - - - - - - + + + + + + + + + + + + + {children} + + + + + + + + + + + + diff --git a/src/state/preferences/show-link-in-handle.tsx b/src/state/preferences/show-link-in-handle.tsx new file mode 100644 index 000000000..74cbd130f --- /dev/null +++ b/src/state/preferences/show-link-in-handle.tsx @@ -0,0 +1,47 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = persisted.Schema['showLinkInHandle'] +type SetContext = (v: persisted.Schema['showLinkInHandle']) => void + +const stateContext = React.createContext( + persisted.defaults.showLinkInHandle, +) +const setContext = React.createContext( + (_: persisted.Schema['showLinkInHandle']) => {}, +) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(persisted.get('showLinkInHandle')) + + const setStateWrapped = React.useCallback( + (showLinkInHandle: persisted.Schema['showLinkInHandle']) => { + setState(showLinkInHandle) + persisted.write('showLinkInHandle', showLinkInHandle) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate('showLinkInHandle', nextShowLinkInHandle => { + setState(nextShowLinkInHandle) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export function useShowLinkInHandle() { + return React.useContext(stateContext) +} + +export function useSetShowLinkInHandle() { + return React.useContext(setContext) +}