import { Avatar } from "@components/UI/Avatar.tsx"; import { Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipTrigger, } from "@components/UI/Tooltip.tsx"; import { MessageState, useDevice, useNodeDB } from "@core/stores"; import type { Message } from "@core/stores/messageStore/types.ts"; import { cn } from "@core/utils/cn.ts"; import { type Protobuf, Types } from "@meshtastic/core"; import type { LucideIcon } from "lucide-react"; import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import { type ReactNode, useMemo } from "react"; import { useTranslation } from "react-i18next"; // import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; // TODO: Uncomment when actions menu is implemented interface MessageStatusInfo { displayText: string; icon: LucideIcon; ariaLabel: string; iconClassName?: string; } const StatusTooltip = ({ statusInfo, children, }: { statusInfo: MessageStatusInfo; children: ReactNode; }) => ( {children} {statusInfo.displayText} ); interface MessageItemProps { message: Message; } export const MessageItem = ({ message }: MessageItemProps) => { const { config } = useDevice(); const { getNode, getMyNode } = useNodeDB(); const { t, i18n } = useTranslation("messages"); const MESSAGE_STATUS_MAP = useMemo( (): Record => ({ [MessageState.Ack]: { displayText: t("deliveryStatus.delivered.displayText"), icon: CheckCircle2, ariaLabel: t("deliveryStatus.delivered.label"), iconClassName: "text-green-500", }, [MessageState.Waiting]: { displayText: t("deliveryStatus.waiting.displayText"), icon: CircleEllipsis, ariaLabel: t("deliveryStatus.waiting.label"), iconClassName: "text-slate-400", }, [MessageState.Failed]: { displayText: t("deliveryStatus.failed.displayText"), icon: AlertCircle, ariaLabel: t("deliveryStatus.failed.label"), iconClassName: "text-red-500 dark:text-red-400", }, }), [t], ); const UNKNOWN_STATUS = useMemo( (): MessageStatusInfo => ({ displayText: t("deliveryStatus.unknown.displayText"), icon: AlertCircle, ariaLabel: t("deliveryStatus.unknown.label"), iconClassName: "text-red-500 dark:text-red-400", }), [t], ); const getMessageStatusInfo = useMemo( () => (state: MessageState): MessageStatusInfo => MESSAGE_STATUS_MAP[state] ?? UNKNOWN_STATUS, [MESSAGE_STATUS_MAP, UNKNOWN_STATUS], ); const messageUser: Protobuf.Mesh.NodeInfo | null | undefined = useMemo(() => { return message.from != null ? getNode(message.from) : null; }, [getNode, message.from]); const myNodeNum = useMemo(() => getMyNode().num, [getMyNode]); const { displayName, shortName, isFavorite } = useMemo(() => { const userIdHex = message.from.toString(16).toUpperCase().padStart(2, "0"); const last4 = userIdHex.slice(-4); const fallbackName = t("fallbackName", { last4 }); const longName = messageUser?.user?.longName; const derivedShortName = messageUser?.user?.shortName || fallbackName; const derivedDisplayName = longName || derivedShortName; const isFavorite = messageUser?.num !== myNodeNum && messageUser?.isFavorite; return { displayName: derivedDisplayName, shortName: derivedShortName, isFavorite: isFavorite, }; }, [messageUser, message.from, t, myNodeNum]); const messageStatusInfo = getMessageStatusInfo(message.state); const StatusIconComponent = messageStatusInfo.icon; const messageDate = useMemo( () => (message.date ? new Date(message.date) : null), [message.date], ); const locale = i18n.language; const formattedTime = useMemo( () => messageDate?.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit", hour12: config?.display?.use12hClock ?? true, }) ?? "", [messageDate, locale, config?.display?.use12hClock], ); const fullDateTime = useMemo( () => messageDate?.toLocaleString(locale, { dateStyle: "medium", timeStyle: "short", }) ?? "", [messageDate, locale], ); const isSender = myNodeNum !== undefined && message.from === myNodeNum; const isOnPrimaryChannel = message.channel === Types.ChannelNumber.Primary; // Use the enum const shouldShowStatusIcon = isSender && isOnPrimaryChannel; const messageItemWrapperClass = cn( "group w-full py-2 relative list-none", "rounded-md", "hover:bg-slate-300/15 dark:hover:bg-slate-600/20", "transition-colors duration-100 ease-in-out", ); const dateTextStyle = "text-xs text-slate-500 dark:text-slate-400"; return (
  • {displayName} {messageDate && ( )} {shouldShowStatusIcon && ( )}
    {message?.message && (
    {message.message}
    )}
    {/* Actions Menu Placeholder */} {/*
    console.log("Reply")} />
    */}
  • ); };