import { memo, useMemo } from 'react'; import { Pressable, Text, View } from 'react-native'; import { Headphones } from 'lucide-react-native'; import type { Particle, StreamProperties } from '@/api/types'; import { isParticleDeleted } from '@/api/types'; import { RelativeTimestamp } from '@/components/RelativeTimestamp'; import { useLiveLatestChild } from '@/hooks/use-particle'; import { useNetwork } from '@/hooks/use-networks'; import { particlePath } from '@/lib/particle-path'; import { cn, getInitials } from '@/lib/utils'; import { useAuthStore } from '@/stores/auth-store'; interface StreamCardProps { particle: Particle & { type: 'stream'; properties: StreamProperties }; networkId: string; onPress: () => void; } /** * Mobile counterpart of js/desktop/src/features/particles/stream-card.tsx — * same data wiring (subscribe to the latest child for unread + initials), * touch-tuned layout (single row, no preview thumbnail in v1). */ export const StreamCard = memo(function StreamCard({ particle, networkId, onPress, }: StreamCardProps) { const streamPath = particlePath(networkId, [particle.id]); const { latestChild } = useLiveLatestChild(streamPath); const userId = useAuthStore((s) => s.user?.id) ?? ''; const network = useNetwork(networkId); const isDM = particle.visible_to.length === 2 && particle.visible_to.every((v) => v.startsWith('human:')); const initials = useMemo(() => { if (isDM) { const otherEntry = particle.visible_to.find( (v) => v !== `human:${userId}`, ); if (otherEntry) { const otherId = otherEntry.replace('human:', ''); const otherHuman = network?.humans?.find((h) => h.id === otherId); if (otherHuman) return getInitials(otherHuman.email); } } if (latestChild) { const creator = network?.humans?.find( (h) => h.id === latestChild.created_by_human_id, ); if (creator) return getInitials(creator.email); } return particle.properties.name.slice(0, 2).toUpperCase(); }, [ isDM, particle.visible_to, particle.properties.name, userId, latestChild, network, ]); const isUnseen = useMemo(() => { if (!latestChild) return false; const latestChildTimestamp = latestChild.created_at.getTime(); const userPlaybackPosition = particle.playback_markers?.[userId]?.getTime() ?? 0; return latestChildTimestamp > userPlaybackPosition; }, [latestChild, particle.playback_markers, userId]); const previewLabel = useMemo(() => { if (!latestChild) return 'No messages yet'; if (isParticleDeleted(latestChild)) return 'Message deleted'; switch (latestChild.type) { case 'media': { const mime = latestChild.properties.mime_type; if (mime.startsWith('image/')) return 'Photo'; const transcriptText = latestChild.properties.transcript?.transcript; if (transcriptText) return transcriptText; return mime.startsWith('audio/') ? 'Voice note' : 'Video clip'; } case 'text': return latestChild.properties.content; case 'file': return latestChild.properties.filename; case 'quest': return latestChild.properties.title; case 'paper': return latestChild.properties.title; default: return 'Update'; } }, [latestChild]); return ( {initials} {particle.properties.name} {previewLabel} {latestChild ? ( ) : null} {(particle.huddle_active_participants?.length ?? 0) > 0 ? ( {particle.huddle_active_participants?.length} ) : isUnseen ? ( ) : null} ); });