/** * Группировка сообщений в rows для FlatList чат-экрана. * * Чистая функция — никаких React-зависимостей, легко тестируется unit'ом. * * Правила: * 1. Между разными календарными днями вставляется {kind:'sep', label}. * 2. Внутри одного дня peer-сообщения группируются в "лесенку" с учётом: * - смены отправителя * - перерыва > 1 часа между соседними сообщениями * В пределах одной группы: * showName = true только у первого * showAvatar = true только у последнего * 3. mine-сообщения всегда idle: showName=false, showAvatar=false * (в референсе X-style никогда не рисуется имя/аватар над своим bubble). * * showName/showAvatar всё равно вычисляются — даже если потом render-слой * их проигнорирует (DM / channel — без sender-meta). Логика кнопки renders * сама решает показывать ли их, см. MessageBubble → withSenderMeta. */ import type { Message } from '@/lib/types'; import { dateBucket } from '@/lib/dates'; export type Row = | { kind: 'sep'; id: string; label: string } | { kind: 'msg'; msg: Message; showName: boolean; showAvatar: boolean; }; // Максимальная пауза внутри "лесенки" — после неё новый run. const RUN_GAP_SECONDS = 60 * 60; // 1 час export function buildRows(msgs: Message[]): Row[] { const out: Row[] = []; let lastBucket = ''; for (let i = 0; i < msgs.length; i++) { const m = msgs[i]; const b = dateBucket(m.timestamp); if (b !== lastBucket) { out.push({ kind: 'sep', id: `sep_${b}_${m.id}`, label: b }); lastBucket = b; } const prev = msgs[i - 1]; const next = msgs[i + 1]; // "Прервать run" флаги: // - разный день // - разный отправитель // - своё vs чужое // - пауза > 1 часа const breakBefore = !prev || dateBucket(prev.timestamp) !== b || prev.from !== m.from || prev.mine !== m.mine || (m.timestamp - prev.timestamp) > RUN_GAP_SECONDS; const breakAfter = !next || dateBucket(next.timestamp) !== b || next.from !== m.from || next.mine !== m.mine || (next.timestamp - m.timestamp) > RUN_GAP_SECONDS; // Для mine — никогда не показываем имя/аватар. const showName = m.mine ? false : breakBefore; const showAvatar = m.mine ? false : breakAfter; out.push({ kind: 'msg', msg: m, showName, showAvatar }); } return out; }