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 ? (
) : (
)}
)
}