import React from 'react';
import { View, Text, StyleSheet, Pressable } from 'react-native';
import { Paperclip, Reply, ReplyAll, Forward, Star } from '../icons';
import type { Email } from '../../api/types';
import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
import { useColors } from '../../theme/colors';
import { useSettingsStore } from '../../stores/settings-store';
import { useLocaleStore } from '../../stores/locale-store';
import SenderAvatar from '../SenderAvatar';
import { MessageContent, type MessageContentProps } from './MessageContent';
import { emailDisplayDate, formatHeaderDate, formatHeaderTime } from '../../lib/email-date';
import { singleLine } from '../../lib/single-line';
interface Props extends MessageContentProps {
expanded: boolean;
onToggleExpanded: () => void;
onReply: (mode: 'reply' | 'replyAll' | 'forward', email: Email) => void;
/**
* The message is not part of a conversation: just its content, without the
* card's collapse handle, reply buttons and header star. Rendered through
* the card so the content survives a conversation arriving around it.
*/
bare?: boolean;
/** The full message is here (not only its header), so it can be replied to. */
replyable?: boolean;
}
/**
* One message of a conversation: a collapsed summary row (sender, date,
* preview) that expands into the full message with its own reply / forward
* actions - the webmail's thread-conversation-view cards.
*/
export function ThreadMessageCard({
expanded, onToggleExpanded, onReply, bare, replyable = true, ...content
}: Props) {
const { email, onToggleStar } = content;
const c = useColors();
const styles = React.useMemo(() => makeStyles(c), [c]);
const t = useLocaleStore((s) => s.t);
const locale = useLocaleStore((s) => s.locale);
const timeFormat = useSettingsStore((s) => s.timeFormat);
const from = email.from?.[0];
const unread = !email.keywords?.$seen;
const starred = !!email.keywords?.$flagged;
const date = emailDisplayDate(email);
if (!expanded) {
return (
[styles.collapsed, pressed && styles.pressed]}>
{from?.name || from?.email || t('email_viewer.unknown_sender', 'Unknown')}
{email.hasAttachment && }
{formatHeaderDate(date, locale)} {formatHeaderTime(date, timeFormat, locale)}
{singleLine(email.preview)}
{starred && }
);
}
// The content keeps its place among the children whether bare or not, so
// it (and its WebView) is not remounted when a conversation arrives.
const reply = (mode: 'reply' | 'replyAll' | 'forward') => (replyable ? () => onReply(mode, email) : undefined);
return (
{!bare && (
)}
{!bare && (
{t('email_viewer.reply', 'Reply')}
{t('email_viewer.reply_all', 'Reply All')}
{t('email_viewer.forward', 'Forward')}
)}
);
}
/**
* Stands in for a collapsed card while a conversation's headers load, at the
* same height, so the opened message does not move when they arrive.
*/
export function ThreadCardPlaceholder() {
const c = useColors();
const styles = React.useMemo(() => makeStyles(c), [c]);
return (
);
}
function makeStyles(c: ThemePalette) {
return StyleSheet.create({
collapsed: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.md,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.md,
backgroundColor: c.surface,
borderBottomWidth: 1,
borderBottomColor: c.border,
},
pressed: { backgroundColor: c.surfaceHover },
collapsedInfo: { flex: 1, minWidth: 0 },
collapsedTop: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs },
collapsedName: { ...typography.body, color: c.text, flex: 1 },
unread: { fontWeight: '700' },
collapsedDate: { ...typography.small, color: c.textMuted },
collapsedPreview: { ...typography.caption, color: c.textMuted, marginTop: 2 },
expanded: {
backgroundColor: c.background,
borderBottomWidth: 1,
borderBottomColor: c.border,
},
bareFill: { flexGrow: 1 },
placeholderAvatar: {
width: componentSizes.avatarSm,
height: componentSizes.avatarSm,
borderRadius: radius.full,
backgroundColor: c.surfaceHover,
},
actionsDisabled: { opacity: 0.4 },
collapseHandle: {
height: 6,
backgroundColor: c.surfaceHover,
borderRadius: radius.xs,
},
actions: {
flexDirection: 'row',
gap: spacing.sm,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
borderTopWidth: 1,
borderTopColor: c.border,
},
actionBtn: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
// Webmail thread card: outline buttons, h-9, rounded-md.
paddingHorizontal: spacing.md,
minHeight: 36,
borderRadius: radius.sm,
borderWidth: 1,
borderColor: c.border,
backgroundColor: 'transparent',
},
actionLabel: { ...typography.bodyMedium, color: c.text },
});
}