'use client'; import React, { useMemo, useState, useRef, useCallback } from 'react'; import { Check, CheckCheck, Clock, AlertCircle, ImageIcon, Video, FileText, Mic, Reply, MoreHorizontal, ChevronDown, Forward, Star, Trash2, Link, Copy, Phone, List, ChevronRight, BarChart2, X, Download, } from 'lucide-react'; import { createPortal } from 'react-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { format } from 'date-fns'; import { Message, RichPayload, NativeBtn } from '../types/inboxTypes'; import { normalizeJid, formatPhone, getMediaUrl, parseSafeDate } from '../utils/inboxUtils'; import LinkPreview, { extractFirstUrl, renderTextWithLinks } from './LinkPreview'; import Avatar from './ui/Avatar'; // ── AudioBubble ─────────────────────────────────────────────────────────────── interface AudioBubbleProps { msg: Message fullMediaUrl: string | null isOut: boolean TimestampRow: React.ReactNode } const WAVEFORM_BARS = 30 const AudioBubble: React.FC = ({ msg, fullMediaUrl, isOut, TimestampRow }) => { const audioRef = useRef(null) const [playing, setPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) const [speed, setSpeed] = useState<1 | 1.5 | 2>(1) const [played, setPlayed] = useState(false) const toggle = useCallback(() => { const el = audioRef.current if (!el) return if (playing) { el.pause() setPlaying(false) } else { el.play().catch(() => {}) setPlaying(true) setPlayed(true) } }, [playing]) const cycleSpeed = useCallback(() => { const el = audioRef.current const next: 1 | 1.5 | 2 = speed === 1 ? 1.5 : speed === 1.5 ? 2 : 1 setSpeed(next) if (el) el.playbackRate = next }, [speed]) const formatTime = (s: number) => { if (!isFinite(s) || s <= 0) return '0:00' const m = Math.floor(s / 60) const sec = Math.floor(s % 60) return `${m}:${sec.toString().padStart(2, '0')}` } const progress = duration > 0 ? currentTime / duration : 0 // Incoming unread PTT = green; played or outgoing = blue const isPtt = msg.type === 'ptt' const accent = isPtt && !isOut && !played ? '#00a884' : '#53bdeb' // Deterministic waveform heights seeded from message_id const waveHeights = useMemo(() => { const seed = msg.message_id.split('').reduce((a, c) => (a + c.charCodeAt(0)) % 97, 0) return Array.from({ length: WAVEFORM_BARS }, (_, i) => { const v = Math.abs(Math.sin((i + seed) * 0.85)) * 0.55 + Math.abs(Math.sin((i * 2.3 + seed) * 0.4)) * 0.45 return Math.max(0.15, Math.min(1, v)) }) }, [msg.message_id]) return (