feat(mobile): Avatar + ConversationRow + relative-time helper

This commit is contained in:
byGalax
2026-05-13 23:59:53 +02:00
parent 3313dd84f3
commit 0b3a6280fc
3 changed files with 99 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
import { StyleSheet, Text, View } from 'react-native';
import { colors } from '../theme/colors';
// Initial-letter avatar circle. Phase 2 will swap in an image-aware
// version that prefers profile.avatarUrl when present.
export function Avatar({ name, size = 40 }: { name: string; size?: number }) {
const letter = (name.trim().charAt(0) || '?').toUpperCase();
return (
<View style={[styles.circle, { width: size, height: size, borderRadius: size / 2 }]}>
<Text style={[styles.letter, { fontSize: size * 0.45 }]}>{letter}</Text>
</View>
);
}
const styles = StyleSheet.create({
circle: {
backgroundColor: colors.accentMuted,
alignItems: 'center',
justifyContent: 'center',
},
letter: { color: colors.accent, fontWeight: '700' },
});
@@ -0,0 +1,58 @@
import type { ConversationSummary } from '@chat-app/shared/chat';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { formatRelativeTime } from '../lib/timeFormat';
import { colors } from '../theme/colors';
import { Avatar } from './Avatar';
interface Props {
conversation: ConversationSummary;
lastMessagePreview: string;
onPress: () => void;
}
export function ConversationRow({ conversation, lastMessagePreview, onPress }: Props) {
const title =
conversation.type === 'group'
? (conversation.name ?? 'Gruppe')
: (conversation.peer?.displayName ?? '—');
const subtitle =
conversation.type === 'group'
? conversation.members.length + ' Mitglieder'
: '@' + (conversation.peer?.username ?? '');
return (
<Pressable
style={({ pressed }) => [styles.row, pressed && { backgroundColor: colors.surface }]}
onPress={onPress}
>
<Avatar name={title} />
<View style={styles.center}>
<View style={styles.titleLine}>
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
<Text style={styles.time}>{formatRelativeTime(conversation.lastMessageAt)}</Text>
</View>
<Text style={styles.preview} numberOfLines={1}>
{lastMessagePreview || subtitle}
</Text>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
gap: 12,
},
center: { flex: 1, minWidth: 0 },
titleLine: { flexDirection: 'row', alignItems: 'baseline', gap: 8 },
title: { color: colors.text, fontSize: 15, fontWeight: '600', flex: 1 },
time: { color: colors.textDim, fontSize: 11 },
preview: { color: colors.textMuted, fontSize: 13, marginTop: 2 },
});