import {useMemo, useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' import { type AppBskyActorDefs, AppBskyActorStatus, type AppBskyEmbedExternal, } from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQuery} from '@tanstack/react-query' import {differenceInMinutes} from 'date-fns' import {getLinkMeta} from '#/lib/link-meta/link-meta' import {cleanError} from '#/lib/strings/errors' import {toNiceDomain} from '#/lib/strings/url-helpers' import {definitelyUrl} from '#/lib/strings/url-helpers' import {useAgent} from '#/state/session' import {useTickEveryMinute} from '#/state/shell' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {atoms as a, platform, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' import {Clock_Stroke2_Corner0_Rounded as ClockIcon} from '#/components/icons/Clock' import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import { useRemoveLiveStatusMutation, useUpsertLiveStatusMutation, } from './queries' import {displayDuration, useDebouncedValue} from './utils' export function EditLiveDialog({ control, status, embed, }: { control: Dialog.DialogControlProps status: AppBskyActorDefs.StatusView embed: AppBskyEmbedExternal.View }) { return ( ) } function DialogInner({ status, embed, }: { status: AppBskyActorDefs.StatusView embed: AppBskyEmbedExternal.View }) { const control = Dialog.useDialogContext() const {_, i18n} = useLingui() const t = useTheme() const agent = useAgent() const [liveLink, setLiveLink] = useState(embed.external.uri) const [liveLinkError, setLiveLinkError] = useState('') const [imageLoadError, setImageLoadError] = useState(false) const tick = useTickEveryMinute() const liveLinkUrl = definitelyUrl(liveLink) const debouncedUrl = useDebouncedValue(liveLinkUrl, 500) const isDirty = liveLinkUrl !== embed.external.uri const { data: linkMeta, isSuccess: hasValidLinkMeta, isLoading: linkMetaLoading, error: linkMetaError, } = useQuery({ enabled: !!debouncedUrl, queryKey: ['link-meta', debouncedUrl], queryFn: async () => { if (!debouncedUrl) return null return getLinkMeta(agent, debouncedUrl) }, }) const record = useMemo(() => { if (!AppBskyActorStatus.isRecord(status.record)) return null const validation = AppBskyActorStatus.validateRecord(status.record) if (validation.success) { return validation.value } return null }, [status]) const { mutate: goLive, isPending: isGoingLive, error: goLiveError, } = useUpsertLiveStatusMutation( record?.durationMinutes ?? 0, linkMeta, record?.createdAt, ) const { mutate: removeLiveStatus, isPending: isRemovingLiveStatus, error: removeLiveStatusError, } = useRemoveLiveStatusMutation() const {minutesUntilExpiry, expiryDateTime} = useMemo(() => { tick! const expiry = new Date(status.expiresAt ?? new Date()) return { expiryDateTime: expiry, minutesUntilExpiry: differenceInMinutes(expiry, new Date()), } }, [tick, status.expiresAt]) const submitDisabled = isGoingLive || !hasValidLinkMeta || debouncedUrl !== liveLinkUrl || isRemovingLiveStatus return ( You are Live {typeof record?.durationMinutes === 'number' ? ( Expires in {displayDuration(i18n, minutesUntilExpiry)} at{' '} {i18n.date(expiryDateTime, { hour: 'numeric', minute: '2-digit', hour12: true, })} ) : ( No expiry set )} Live link setLiveLinkError('')} onBlur={() => { if (!definitelyUrl(liveLink)) { setLiveLinkError('Invalid URL') } }} returnKeyType="done" autoCapitalize="none" autoComplete="url" autoCorrect={false} onSubmitEditing={() => { if (isDirty && !submitDisabled) { goLive() } }} /> {(liveLinkError || linkMetaError) && ( {liveLinkError ? ( This is not a valid link ) : ( cleanError(linkMetaError) )} )} {(linkMeta || linkMetaLoading) && ( {(!linkMeta || linkMeta.image) && ( {linkMeta?.image && ( setImageLoadError(false)} onError={() => setImageLoadError(true)} /> )} {linkMeta && imageLoadError && ( )} )} {linkMeta ? ( <> {linkMeta.title || linkMeta.url} {toNiceDomain(linkMeta.url)} ) : ( <> )} )} {goLiveError && ( {cleanError(goLiveError)} )} {removeLiveStatusError && ( {cleanError(removeLiveStatusError)} )} {isDirty ? ( ) : ( )} ) }