import React, { useCallback, useEffect, useState } from "react"; import { Image, Pressable, Text, View } from "react-native"; import { Message } from "../../types"; import { playAudio, stopPlayback, onPlayingChange } from "../../services/audio"; import { ImageViewer } from "./ImageViewer"; import { useTheme } from "../../contexts/ThemeContext"; interface MessageBubbleProps { message: Message; } function formatDuration(ms?: number): string { if (!ms) return "0:00"; const totalSeconds = Math.floor(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}:${seconds.toString().padStart(2, "0")}`; } function formatTime(timestamp: number): string { const d = new Date(timestamp); return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } export function MessageBubble({ message }: MessageBubbleProps) { const [isPlaying, setIsPlaying] = useState(false); const [showViewer, setShowViewer] = useState(false); const { colors, isDark } = useTheme(); useEffect(() => { return onPlayingChange((playing) => { if (!playing) setIsPlaying(false); }); }, []); const isUser = message.role === "user"; const isSystem = message.role === "system"; const handleVoicePress = useCallback(async () => { if (!message.audioUri) return; if (isPlaying) { await stopPlayback(); setIsPlaying(false); } else { setIsPlaying(true); await playAudio(message.audioUri, () => setIsPlaying(false)); } }, [isPlaying, message.audioUri]); if (isSystem) { return ( {message.content} ); } const bubbleBg = isUser ? colors.accent : isDark ? "#252538" : colors.bgSecondary; const bubbleRadius = isUser ? { borderTopRightRadius: 4 } : { borderTopLeftRadius: 4 }; return ( {message.type === "image" && message.imageBase64 ? ( setShowViewer(true)}> {message.content ? ( {message.content} ) : null} setShowViewer(false)} /> ) : message.type === "voice" ? ( {isPlaying ? "\u23F8" : "\u25B6"} {Array.from({ length: 20 }).map((_, i) => ( ))} {formatDuration(message.duration)} ) : ( {message.content} )} {formatTime(message.timestamp)} {isUser && message.status === "error" && ( ! )} ); }