import {Platform, View} from 'react-native' import {Image} from 'expo-image' import { type AppBskyActorDefs, type AppBskyActorGetProfile, type AtpAgent, } from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useMutation, useQueryClient} from '@tanstack/react-query' import {until} from '#/lib/async/until' import {isNetworkError} from '#/lib/strings/errors' import {RQKEY} from '#/state/queries/profile' import {useAgent, useSession} from '#/state/session' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {CustomLinkWarningDialog} from '#/components/dialogs/LinkWarning' import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRightIcon} from '#/components/icons/Arrow' import {Link} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import type * as bsky from '#/types/bsky' export function GermButton({ germ, profile, }: { germ: AppBskyActorDefs.ProfileAssociatedGerm profile: bsky.profile.AnyProfileView }) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() const {currentAccount} = useSession() const linkWarningControl = Dialog.useDialogControl() // exclude `none` and all unknown values if ( !(germ.showButtonTo === 'everyone' || germ.showButtonTo === 'usersIFollow') ) { return null } if (currentAccount?.did === profile.did) { return } if (germ.showButtonTo === 'usersIFollow' && !profile.viewer?.followedBy) { return null } const url = constructGermUrl(germ, profile, currentAccount?.did) if (!url) { return null } return ( <> { ax.metric('profile:associated:germ:click-to-chat', {}) if (isCustomGermDomain(url)) { evt.preventDefault() linkWarningControl.open() return false } }} label={_(msg`Open Germ DM`)} overridePresentation={false} shouldProxy={false} style={[ t.atoms.bg_contrast_50, a.rounded_full, a.self_start, {padding: 6}, ]}> Germ DM ) } function GermLogo({size}: {size: 'small' | 'large'}) { return ( ) } function GermSelfButton({did}: {did: string}) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() const selfExplanationDialogControl = Dialog.useDialogControl() const agent = useAgent() const queryClient = useQueryClient() const {mutate: deleteDeclaration, isPending} = useMutation({ mutationFn: async () => { const previousRecord = await agent.com.germnetwork.declaration .get({ repo: did, rkey: 'self', }) .then(res => res.value) .catch(() => null) await agent.com.germnetwork.declaration.delete({ repo: did, rkey: 'self', }) await whenAppViewReady(agent, did, res => !res.data.associated?.germ) return previousRecord }, onSuccess: previousRecord => { ax.metric('profile:associated:germ:self-disconnect', {}) async function undo() { if (!previousRecord) return try { await agent.com.germnetwork.declaration.put( { repo: did, rkey: 'self', }, previousRecord, ) await whenAppViewReady(agent, did, res => !!res.data.associated?.germ) await queryClient.refetchQueries({queryKey: RQKEY(did)}) Toast.show(_(msg`Germ DM reconnected`)) ax.metric('profile:associated:germ:self-reconnect', {}) } catch (e: any) { Toast.show( _(msg`Failed to reconnect Germ DM. Error: ${e?.message}`), { type: 'error', }, ) if (!isNetworkError(e)) { ax.logger.error('Failed to reconnect Germ DM link', { safeMessage: e, }) } } } selfExplanationDialogControl.close(() => { void queryClient.refetchQueries({queryKey: RQKEY(did)}) Toast.show( Germ DM disconnected {previousRecord && ( void undo()}> Undo )} , ) }) }, onError: error => { Toast.show( _(msg`Failed to disconnect Germ DM. Error: ${error?.message}`), { type: 'error', }, ) if (!isNetworkError(error)) { ax.logger.error('Failed to disconnect Germ DM link', { safeMessage: error, }) } }, }) return ( <> Germ DM Link This button lets others open the Germ DM app to send you a message. You can manage its visibility from the Germ DM app, or you can disconnect your Bluesky account from Germ DM altogether by clicking the button below. ) } function constructGermUrl( declaration: AppBskyActorDefs.ProfileAssociatedGerm, profile: bsky.profile.AnyProfileView, viewerDid?: string, ) { try { const urlp = new URL(declaration.messageMeUrl) if (urlp.pathname.endsWith('/')) { urlp.pathname = urlp.pathname.slice(0, -1) } urlp.pathname += `/${platform()}` if (viewerDid) { urlp.hash = `#${profile.did}+${viewerDid}` } else { urlp.hash = `#${profile.did}` } return urlp.toString() } catch { return null } } function isCustomGermDomain(url: string) { try { const urlp = new URL(url) return urlp.hostname !== 'landing.ger.mx' } catch { return false } } function platform() { switch (Platform.OS) { case 'ios': return 'iOS' case 'android': return 'android' default: return 'web' } } async function whenAppViewReady( agent: AtpAgent, actor: string, fn: (res: AppBskyActorGetProfile.Response) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, () => agent.app.bsky.actor.getProfile({actor}), ) }