import {useCallback, useEffect, useMemo, useState} from 'react'
import {Pressable, View} from 'react-native'
import * as VideoThumbnails from 'expo-video-thumbnails'
import {msg, plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import * as device from '#/lib/deviceName'
import {logger} from '#/view/com/composer/drafts/state/logger'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, select, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {CirclePlus_Stroke2_Corner0_Rounded as CirclePlusIcon} from '#/components/icons/CirclePlus'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsIcon} from '#/components/icons/DotGrid'
import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import * as MediaPreview from '#/components/MediaPreview'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import {type DraftPostDisplay, type DraftSummary} from './state/schema'
import * as storage from './state/storage'
export function DraftItem({
draft,
onSelect,
onDelete,
}: {
draft: DraftSummary
onSelect: (draft: DraftSummary) => void
onDelete: (draft: DraftSummary) => void
}) {
const {_} = useLingui()
const t = useTheme()
const discardPromptControl = Prompt.usePromptControl()
const post = draft.posts[0]
const mediaExistsOnOtherDevice =
!draft.meta.isOriginatingDevice && draft.meta.hasMissingMedia
const mediaIsMissing =
draft.meta.isOriginatingDevice && draft.meta.hasMissingMedia
const hasMetadata =
draft.meta.replyCount > 0 ||
mediaExistsOnOtherDevice ||
draft.meta.hasQuotes
const isUnknownDevice = useMemo(() => {
const raw = draft.draft.deviceName
switch (raw) {
case device.FALLBACK_IOS:
case device.FALLBACK_ANDROID:
case device.FALLBACK_WEB:
return true
default:
return false
}
}, [draft])
const handleDelete = useCallback(() => {
onDelete(draft)
}, [onDelete, draft])
return (
<>
onSelect(draft)}
style={({pressed, hovered}) => [
a.rounded_md,
a.border,
t.atoms.shadow_sm,
pressed || hovered
? t.atoms.border_contrast_medium
: t.atoms.border_contrast_low,
{
backgroundColor: select(t.name, {
light: t.atoms.bg.backgroundColor,
dark: t.atoms.bg_contrast_25.backgroundColor,
dim: t.atoms.bg_contrast_25.backgroundColor,
}),
},
]}>
{!!post.text.trim().length && (
)}
{!mediaExistsOnOtherDevice && }
{hasMetadata && (
{mediaExistsOnOtherDevice && (
)}
{mediaIsMissing && (
)}
{draft.meta.hasQuotes && (
)}
{draft.meta.replyCount > 0 && (
)}
)}
{/* Timestamp */}
{({timeElapsed}) => (
{timeElapsed}
)}
{/* Menu button */}
>
)
}
function DraftMetadataTag({
display = 'info',
icon: Icon,
text,
}: {
display?: 'info' | 'warning'
icon: React.ComponentType
text: string
}) {
const t = useTheme()
const color = {
info: t.atoms.text_contrast_medium.color,
warning: select(t.name, {
light: '#C99A00',
dark: t.palette.yellow,
dim: t.palette.yellow,
}),
}[display]
return (
{text}
)
}
type LoadedImage = {
url: string
alt: string
}
function DraftMediaPreview({post}: {post: DraftPostDisplay}) {
const [loadedImages, setLoadedImages] = useState([])
const [videoThumbnail, setVideoThumbnail] = useState()
useEffect(() => {
async function loadMedia() {
if (post.images && post.images.length > 0) {
const loaded: LoadedImage[] = []
for (const image of post.images) {
try {
const url = await storage.loadMediaFromLocal(image.localPath)
loaded.push({url, alt: image.altText || ''})
} catch (e) {
// Image doesn't exist locally, skip it
}
}
setLoadedImages(loaded)
}
if (post.video?.exists && post.video.localPath) {
try {
const url = await storage.loadMediaFromLocal(post.video.localPath)
if (IS_WEB) {
// can't generate thumbnails on web
setVideoThumbnail("yep, there's a video")
} else {
logger.debug('generating thumbnail of ', {url})
const thumbnail = await VideoThumbnails.getThumbnailAsync(url, {
time: 0,
quality: 0.2,
})
logger.debug('thumbnail generated', {thumbnail})
setVideoThumbnail(thumbnail.uri)
}
} catch (e) {
// Video doesn't exist locally
}
}
}
void loadMedia()
}, [post.images, post.video])
// Nothing to show
if (loadedImages.length === 0 && !post.gif && !post.video) {
return null
}
return (
{loadedImages.map((image, i) => (
))}
{post.gif && (
)}
{post.video && videoThumbnail && (
)}
)
}