import { memo, useMemo } from "react"; import { Pressable, Text, View } from "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": return latestChild.properties.mime_type.startsWith("audio/") ? "Voice message" : "Video message"; 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} {isUnseen ? ( ) : null} ); });