import { useMemo } from 'react'; import { Image, Pressable, StyleSheet, Text, useWindowDimensions, View, } from 'react-native'; import { fonts, m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme'; import { AppIcon } from './app-icon'; import { AppLoadingSpinner } from './AppLoadingSpinner'; import { StoryTextGlyph } from './story-glyphs'; export type ComposeRecentMediaItem = { id: string; uri: string; mediaType: 'photo' | 'video'; duration?: number; }; const GRID_COLUMNS = 3; const GRID_GAP = 2; const GRID_PANEL_MIN_HEIGHT = 220; const GRID_MAX_CELLS = 30; type GridCell = | { id: string; kind: 'camera' } | { id: string; kind: 'text' } | (ComposeRecentMediaItem & { kind: 'asset' }); function chunkGridRows(cells: GridCell[], columns: number): GridCell[][] { const rows: GridCell[][] = []; for (let index = 0; index < cells.length; index += columns) { rows.push(cells.slice(index, index + columns)); } return rows; } export function ComposeRecentMediaGrid({ items, loading, selectedId, selectedIds, emptyHint, onSelectCamera, onSelectText, onSelectItem, onSelectFromLibrary, }: { items: ComposeRecentMediaItem[]; loading?: boolean; selectedId?: string | null; selectedIds?: string[]; emptyHint?: string; onSelectCamera: () => void; onSelectText?: () => void; onSelectItem: (item: ComposeRecentMediaItem) => void; onSelectFromLibrary: () => void; }) { const { width } = useWindowDimensions(); const cellSize = useMemo(() => { const horizontalPad = spacing.md * 2; return Math.floor((width - horizontalPad - GRID_GAP * (GRID_COLUMNS - 1)) / GRID_COLUMNS); }, [width]); const gridRows = useMemo(() => { const reservedSlots = 1 + (onSelectText ? 1 : 0); const cells: GridCell[] = [ { id: '__camera__', kind: 'camera' }, ...(onSelectText ? [{ id: '__text__', kind: 'text' as const }] : []), ...items.slice(0, GRID_MAX_CELLS - reservedSlots).map((item) => ({ ...item, kind: 'asset' as const })), ]; return chunkGridRows(cells, GRID_COLUMNS); }, [items, onSelectText]); return ( Recentes [styles.selectBtn, pressed && { opacity: 0.85 }]} > Selecionar {loading ? ( ) : ( {items.length === 0 && emptyHint ? ( {emptyHint} ) : null} {gridRows.map((row, rowIndex) => ( {row.map((item) => { if (item.kind === 'camera') { return ( [ styles.cell, { width: cellSize, height: cellSize }, pressed && { opacity: 0.85 }, ]} > ); } if (item.kind === 'text') { return ( [ styles.cell, { width: cellSize, height: cellSize }, pressed && { opacity: 0.85 }, ]} > Texto ); } const selected = selectedIds?.includes(item.id) || selectedId === item.id; return ( onSelectItem(item)} accessibilityRole="button" accessibilityLabel={item.mediaType === 'video' ? 'Selecionar vídeo' : 'Selecionar foto'} style={({ pressed }) => [ styles.cell, { width: cellSize, height: cellSize }, selected && styles.cellSelected, pressed && { opacity: 0.9 }, ]} > {item.mediaType === 'video' ? ( {formatVideoDuration(item.duration)} ) : null} ); })} ))} )} ); } function formatVideoDuration(duration?: number): string { if (!duration || duration <= 0) return 'Vídeo'; const total = Math.round(duration); const minutes = Math.floor(total / 60); const seconds = total % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; } const styles = StyleSheet.create({ panel: { marginTop: spacing.md, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: m3Colors.outlineVariant, backgroundColor: m3Colors.surfaceContainer, paddingTop: spacing.sm, marginHorizontal: -spacing.md, paddingHorizontal: spacing.md, paddingBottom: spacing.sm, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: spacing.sm, }, albumLabel: { color: m3Colors.onSurface, ...m3Type.label, }, selectBtn: { paddingHorizontal: spacing.md, paddingVertical: spacing.xs, borderRadius: m3Shape.cornerFull, backgroundColor: m3Colors.surfaceContainerHigh, borderWidth: 1, borderColor: m3Colors.outlineVariant, }, selectLabel: { color: m3Colors.onSurface, fontFamily: fonts.medium, fontSize: 13, }, loadingWrap: { alignItems: 'center', justifyContent: 'center', }, emptyHint: { color: m3Colors.onSurfaceVariant, ...m3Type.bodySmall, marginBottom: spacing.sm, }, grid: { gap: GRID_GAP, }, gridRow: { flexDirection: 'row', gap: GRID_GAP, }, cell: { borderRadius: m3Shape.cornerSmall, backgroundColor: m3Colors.surfaceContainerHigh, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, cellSelected: { borderWidth: 2, borderColor: m3Colors.primary, }, thumb: { width: '100%', height: '100%', }, videoBadge: { position: 'absolute', left: spacing.xs, bottom: spacing.xs, paddingHorizontal: spacing.xs, paddingVertical: 2, borderRadius: m3Shape.cornerExtraSmall, backgroundColor: m3Colors.scrim, }, videoBadgeText: { color: m3Colors.onPrimary, fontFamily: fonts.medium, fontSize: 10, }, textCellLabel: { marginTop: spacing.xs, color: m3Colors.onSurfaceVariant, fontFamily: fonts.medium, fontSize: 11, }, });