import React, { useEffect, useRef } from 'react'; import { ActivityIndicator, Animated, Image, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { useTheme } from '../../../theme'; import { useCompTheme } from '../../../theme/hook'; import { CometChatTheme } from '../../../theme/type'; import { SelectedAttachment } from '../../modals/SelectedAttachment'; import { deepMerge } from '../../helper/helperFunctions'; export interface CometChatAttachmentPreviewItemProps { attachment: SelectedAttachment; onRemove: (fileId: string) => void; onRetry: (fileId: string) => void; /** Custom styles for the attachment preview item, overriding theme styles. */ style?: CometChatTheme["attachmentPreviewItemStyles"]; } // Intentional media scrims/overlays painted over attachment thumbnails (not UI-surface colors) — // no theme-token equivalent, so kept as documented consts per the project's OVERLAY_COLOR convention. const OVERLAY_COLOR = 'rgba(0,0,0,0.45)'; const PLAY_GLYPH_DIM = 'rgba(255,255,255,0.8)'; // dimmed white ▶ over a loading thumbnail const REJECTED_SCRIM_COLOR = 'rgba(180,0,0,0.55)'; // red scrim over a REJECTED tile const REMOVE_BADGE_BG = 'rgba(0,0,0,0.55)'; // ✕ remove-badge backing export const CometChatAttachmentPreviewItem = ({ attachment, onRemove, onRetry, style, }: CometChatAttachmentPreviewItemProps) => { const theme = useTheme(); const compTheme = useCompTheme(); const s = deepMerge( theme.attachmentPreviewItemStyles ?? {}, compTheme.attachmentPreviewItemStyles ?? {}, style ?? {} ); const ITEM_SIZE = theme.spacing.spacing.s18; const BORDER_RADIUS = theme.spacing.radius.r2; const { fileId, file, uploadState, uploadProgress } = attachment; const checkmarkOpacity = useRef(new Animated.Value(0)).current; useEffect(() => { if (uploadState === 'COMPLETED') { checkmarkOpacity.setValue(1); Animated.delay(1000).start(() => { Animated.timing(checkmarkOpacity, { toValue: 0, duration: 400, useNativeDriver: true, }).start(); }); } }, [uploadState]); const isMedia = file.type.startsWith('image/') || file.type.startsWith('video/'); return ( {/* Thumbnail */} {isMedia ? ( ) : ( {file.name} )} {/* Video play icon */} {file.type.startsWith('video/') && ( {'▶'} )} {/* NONE state — indeterminate spinner */} {uploadState === 'NONE' && ( )} {/* UPLOADING state — progress arc + percentage */} {uploadState === 'UPLOADING' && ( {`${Math.round(uploadProgress)}%`} )} {/* COMPLETED state — fading checkmark */} {'✓'} {/* FAILED state — warning icon, tap to retry */} {uploadState === 'FAILED' && ( onRetry(fileId)} activeOpacity={0.8} style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: OVERLAY_COLOR, justifyContent: 'center', alignItems: 'center', }} accessibilityRole="button" accessibilityLabel="Retry upload" > {'⚠'} )} {/* REJECTED state — permanent failure, no retry (remove via × button) */} {uploadState === 'REJECTED' && ( {'⛔'} )} {/* Remove (×) button — always visible */} onRemove(fileId)} hitSlop={{ top: theme.spacing.spacing.s2, right: theme.spacing.spacing.s2, bottom: theme.spacing.spacing.s2, left: theme.spacing.spacing.s2, }} style={[{ position: 'absolute', top: theme.spacing.spacing.s1, right: theme.spacing.spacing.s1, width: theme.spacing.spacing.s4, height: theme.spacing.spacing.s4, borderRadius: theme.spacing.radius.r2, backgroundColor: REMOVE_BADGE_BG, justifyContent: 'center', alignItems: 'center', }, s.removeButtonStyle]} accessibilityRole="button" accessibilityLabel="Remove attachment" > {'✕'} ); }; const styles = StyleSheet.create({ fillCenter: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', }, });