/** * AttachmentPreview — рендер `Message.attachment` внутри bubble'а. * * Четыре формы: * - image → Image с object-fit cover, aspect-ratio из width/height * - video → то же + play-overlay в центре, duration внизу-справа * - voice → row [play-icon] [waveform stub] [duration] * - file → row [file-icon] [name + size] * * Вложения размещаются ВНУТРИ того же bubble'а что и текст, чуть ниже * footer'а нет и ширина bubble'а снимает maxWidth-ограничение ради * изображений (отдельный media-first-bubble case). */ import React from 'react'; import { View, Text, Image } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import type { Attachment } from '@/lib/types'; import { VoicePlayer } from '@/components/chat/VoicePlayer'; import { VideoCirclePlayer } from '@/components/chat/VideoCirclePlayer'; export interface AttachmentPreviewProps { attachment: Attachment; /** Используется для тонирования footer-элементов. */ own?: boolean; } function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`; return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`; } function formatDuration(seconds: number): string { const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m}:${String(s).padStart(2, '0')}`; } export function AttachmentPreview({ attachment, own }: AttachmentPreviewProps) { switch (attachment.kind) { case 'image': return ; case 'video': // circle=true — круглое видео-сообщение (Telegram-стиль). return attachment.circle ? : ; case 'voice': return ; case 'file': return ; } } // ─── Image ────────────────────────────────────────────────────────── function ImageAttachment({ att }: { att: Attachment }) { // Aspect-ratio из реальных width/height; fallback 4:3. const aspect = att.width && att.height ? att.width / att.height : 4 / 3; return ( ); } // ─── Video ────────────────────────────────────────────────────────── function VideoAttachment({ att }: { att: Attachment }) { const aspect = att.width && att.height ? att.width / att.height : 16 / 9; return ( {/* Play overlay по центру */} {att.duration !== undefined && ( {formatDuration(att.duration)} )} ); } // ─── Voice ────────────────────────────────────────────────────────── // Реальный плеер — см. components/chat/VoicePlayer.tsx (expo-av Sound). // ─── File ─────────────────────────────────────────────────────────── function FileAttachment({ att, own }: { att: Attachment; own?: boolean }) { return ( {att.name ?? 'file'} {att.size !== undefined ? formatSize(att.size) : ''} {att.size !== undefined && att.mime ? ' · ' : ''} {att.mime ?? ''} ); }