feat(chat): redesign chat list + conversation view
- RoomCard / chat.tsx DmItem: cleaner list rows (48px avatar, minHeight 68, consistent padding, time next to name, TouchableOpacity) - ChatBubble: timestamp inline under content (no absolute-position hack), borderRadius 20, 28px avatar, lighter backdrop - ChatInput: surface bg, hairline-bordered input pill, 38px send button Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7369912d60
commit
2dcff6408c
@ -3,7 +3,6 @@ import {
|
|||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
FlatList,
|
FlatList,
|
||||||
Pressable,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Image,
|
Image,
|
||||||
@ -45,51 +44,51 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }
|
|||||||
const hasUnread = conv.unreadCount > 0;
|
const hasUnread = conv.unreadCount > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable onPress={onPress} android_ripple={{ color: '#f5f5f5' }}>
|
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
||||||
<View style={styles.dmRow}>
|
<View style={styles.dmRow}>
|
||||||
<View style={styles.dmAvatar}>
|
<View style={styles.dmAvatar}>
|
||||||
{conv.partnerAvatar ? (
|
{conv.partnerAvatar ? (
|
||||||
<Image source={{ uri: conv.partnerAvatar }} style={styles.dmAvatarImg} />
|
<Image source={{ uri: conv.partnerAvatar }} style={styles.dmAvatarImg} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.dmAvatarInitials}>
|
<Text style={styles.dmAvatarInitials}>
|
||||||
{conv.partnerName.slice(0, 2).toUpperCase()}
|
{conv.partnerName.slice(0, 2).toUpperCase()}
|
||||||
</Text>
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View style={styles.dmInfo}>
|
||||||
|
<View style={styles.dmHeaderRow}>
|
||||||
|
<Text style={styles.dmName} numberOfLines={1}>
|
||||||
|
{conv.partnerName}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.dmTime, { color: hasUnread ? '#007AFF' : colors.textMuted }]}>
|
||||||
|
{formatTime(conv.lastMessageAt, t('chat.just_now'))}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.dmBottomRow}>
|
||||||
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={[
|
||||||
|
styles.dmLast,
|
||||||
|
{
|
||||||
|
fontFamily: hasUnread ? 'Nunito_600SemiBold' : 'Nunito_400Regular',
|
||||||
|
color: hasUnread ? colors.text : colors.textMuted,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{conv.isOwn ? `${t('chat.you')} ` : ''}
|
||||||
|
{conv.lastMessage}
|
||||||
|
</Text>
|
||||||
|
{hasUnread && (
|
||||||
|
<View style={styles.unreadBadge}>
|
||||||
|
<Text style={styles.unreadBadgeText}>
|
||||||
|
{conv.unreadCount > 99 ? '99+' : conv.unreadCount}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.dmInfo}>
|
</View>
|
||||||
<View style={styles.dmHeaderRow}>
|
|
||||||
<Text style={styles.dmName} numberOfLines={1}>
|
|
||||||
{conv.partnerName}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[styles.dmTime, { color: hasUnread ? '#007AFF' : '#a3a3a3' }]}
|
|
||||||
>
|
|
||||||
{formatTime(conv.lastMessageAt, t('chat.just_now'))}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.dmBottomRow}>
|
|
||||||
<Text
|
|
||||||
numberOfLines={1}
|
|
||||||
style={[
|
|
||||||
styles.dmLast,
|
|
||||||
{
|
|
||||||
fontFamily: hasUnread ? 'Nunito_600SemiBold' : 'Nunito_400Regular',
|
|
||||||
color: hasUnread ? '#171717' : '#a3a3a3',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{conv.isOwn ? t('chat.you') : ''}
|
|
||||||
{conv.lastMessage}
|
|
||||||
</Text>
|
|
||||||
{hasUnread && (
|
|
||||||
<View style={styles.unreadBadge}>
|
|
||||||
<Text style={styles.unreadBadgeText}>{conv.unreadCount}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,28 +351,29 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
},
|
},
|
||||||
dmRow: {
|
dmRow: {
|
||||||
width: '100%',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingHorizontal: 14,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 11,
|
paddingVertical: 12,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
borderBottomColor: colors.border,
|
borderBottomColor: colors.border,
|
||||||
|
minHeight: 68,
|
||||||
},
|
},
|
||||||
dmAvatar: {
|
dmAvatar: {
|
||||||
width: 42,
|
width: 48,
|
||||||
height: 42,
|
height: 48,
|
||||||
borderRadius: 21,
|
borderRadius: 24,
|
||||||
backgroundColor: colors.surfaceElevated,
|
backgroundColor: colors.surfaceElevated,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginRight: 10,
|
marginRight: 12,
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
dmAvatarImg: { width: 42, height: 42 },
|
dmAvatarImg: { width: 48, height: 48 },
|
||||||
dmAvatarInitials: {
|
dmAvatarInitials: {
|
||||||
fontSize: 13,
|
fontSize: 15,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
},
|
},
|
||||||
@ -384,13 +384,13 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
dmName: {
|
dmName: {
|
||||||
fontSize: 14,
|
fontSize: 15,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
marginRight: 6,
|
marginRight: 6,
|
||||||
},
|
},
|
||||||
dmTime: { fontSize: 11, fontFamily: 'Nunito_600SemiBold' },
|
dmTime: { fontSize: 11, fontFamily: 'Nunito_500Medium' },
|
||||||
dmBottomRow: {
|
dmBottomRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
Image,
|
Image,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Modal,
|
Modal,
|
||||||
Alert,
|
|
||||||
Platform,
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import * as Clipboard from 'expo-clipboard';
|
import * as Clipboard from 'expo-clipboard';
|
||||||
@ -68,7 +67,6 @@ export function ChatBubble({
|
|||||||
const colors = useColors();
|
const colors = useColors();
|
||||||
const styles = makeStyles(colors);
|
const styles = makeStyles(colors);
|
||||||
const [actionsOpen, setActionsOpen] = useState(false);
|
const [actionsOpen, setActionsOpen] = useState(false);
|
||||||
const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
||||||
|
|
||||||
const isImageOnly =
|
const isImageOnly =
|
||||||
!!msg.attachmentUrl && msg.attachmentType === 'image' && !msg.content && !msg.replyTo;
|
!!msg.attachmentUrl && msg.attachmentType === 'image' && !msg.content && !msg.replyTo;
|
||||||
@ -234,24 +232,25 @@ export function ChatBubble({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.content,
|
styles.content,
|
||||||
{ color: msg.isOwn ? '#ffffff' : '#171717', paddingRight: 48 },
|
{ color: msg.isOwn ? '#ffffff' : colors.text },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{msg.content}
|
{msg.content}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer: timestamp + read-receipt inline below content */}
|
||||||
{!isImageOnly && (
|
{!isImageOnly && (
|
||||||
<View style={styles.footer}>
|
<View style={[styles.footer, { justifyContent: msg.isOwn ? 'flex-end' : 'flex-start' }]}>
|
||||||
{msg.likesCount > 0 && (
|
{msg.likesCount > 0 && (
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginRight: 3 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', marginRight: 4 }}>
|
||||||
<Ionicons name="heart" size={9} color="#f87171" />
|
<Ionicons name="heart" size={10} color="#f87171" />
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 9,
|
fontSize: 10,
|
||||||
marginLeft: 1,
|
marginLeft: 2,
|
||||||
color: msg.isOwn ? 'rgba(255,255,255,0.7)' : '#a3a3a3',
|
fontFamily: 'Nunito_600SemiBold',
|
||||||
|
color: msg.isOwn ? 'rgba(255,255,255,0.75)' : '#a3a3a3',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{msg.likesCount}
|
{msg.likesCount}
|
||||||
@ -260,8 +259,9 @@ export function ChatBubble({
|
|||||||
)}
|
)}
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 9,
|
fontSize: 10,
|
||||||
color: msg.isOwn ? 'rgba(255,255,255,0.65)' : '#a3a3a3',
|
fontFamily: 'Nunito_400Regular',
|
||||||
|
color: msg.isOwn ? 'rgba(255,255,255,0.7)' : colors.textMuted,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatTime(msg.createdAt)}
|
{formatTime(msg.createdAt)}
|
||||||
@ -269,9 +269,9 @@ export function ChatBubble({
|
|||||||
{msg.isOwn && !hideReadStatus && (
|
{msg.isOwn && !hideReadStatus && (
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name={msg.readAt ? 'checkmark-done' : 'checkmark'}
|
name={msg.readAt ? 'checkmark-done' : 'checkmark'}
|
||||||
size={11}
|
size={12}
|
||||||
color={msg.readAt ? '#93c5fd' : 'rgba(255,255,255,0.65)'}
|
color={msg.readAt ? '#93c5fd' : 'rgba(255,255,255,0.7)'}
|
||||||
style={{ marginLeft: 2 }}
|
style={{ marginLeft: 3 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@ -335,36 +335,37 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
return StyleSheet.create({
|
return StyleSheet.create({
|
||||||
row: {
|
row: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 10,
|
||||||
},
|
},
|
||||||
avatarSlot: {
|
avatarSlot: {
|
||||||
width: 30,
|
width: 32,
|
||||||
marginRight: 4,
|
marginRight: 6,
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
width: 26,
|
width: 28,
|
||||||
height: 26,
|
height: 28,
|
||||||
borderRadius: 13,
|
borderRadius: 14,
|
||||||
backgroundColor: colors.surfaceElevated,
|
backgroundColor: colors.surfaceElevated,
|
||||||
},
|
},
|
||||||
bubbleCol: {
|
bubbleCol: {
|
||||||
maxWidth: '78%',
|
maxWidth: '76%',
|
||||||
},
|
},
|
||||||
nickname: {
|
nickname: {
|
||||||
fontSize: 10,
|
fontSize: 11,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: '#007AFF',
|
color: '#007AFF',
|
||||||
marginBottom: 2,
|
marginBottom: 3,
|
||||||
marginLeft: 10,
|
marginLeft: 12,
|
||||||
},
|
},
|
||||||
bubble: {
|
bubble: {
|
||||||
borderRadius: 18,
|
borderRadius: 20,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 14,
|
||||||
paddingVertical: 6,
|
paddingTop: 8,
|
||||||
|
paddingBottom: 6,
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOpacity: 0.05,
|
shadowOpacity: 0.06,
|
||||||
shadowRadius: 1,
|
shadowRadius: 2,
|
||||||
shadowOffset: { width: 0, height: 1 },
|
shadowOffset: { width: 0, height: 1 },
|
||||||
},
|
},
|
||||||
bubbleOwn: {
|
bubbleOwn: {
|
||||||
@ -380,10 +381,10 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
marginBottom: 4,
|
marginBottom: 6,
|
||||||
},
|
},
|
||||||
imageWrap: {
|
imageWrap: {
|
||||||
borderRadius: 12,
|
borderRadius: 14,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
@ -405,27 +406,26 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 20,
|
lineHeight: 21,
|
||||||
fontFamily: 'Nunito_400Regular',
|
fontFamily: 'Nunito_400Regular',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
position: 'absolute',
|
|
||||||
bottom: 4,
|
|
||||||
right: 8,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
gap: 3,
|
||||||
|
marginTop: 4,
|
||||||
},
|
},
|
||||||
sheetBackdrop: {
|
sheetBackdrop: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
backgroundColor: 'rgba(0,0,0,0.35)',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
},
|
},
|
||||||
sheet: {
|
sheet: {
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
borderTopLeftRadius: 20,
|
borderTopLeftRadius: 22,
|
||||||
borderTopRightRadius: 20,
|
borderTopRightRadius: 22,
|
||||||
padding: 8,
|
padding: 8,
|
||||||
paddingBottom: Platform.OS === 'ios' ? 32 : 16,
|
paddingBottom: Platform.OS === 'ios' ? 34 : 16,
|
||||||
},
|
},
|
||||||
sheetGrabber: {
|
sheetGrabber: {
|
||||||
width: 36,
|
width: 36,
|
||||||
|
|||||||
@ -240,7 +240,7 @@ function decodeBase64(base64: string): Uint8Array {
|
|||||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||||
return StyleSheet.create({
|
return StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.surface,
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
borderTopColor: colors.border,
|
borderTopColor: colors.border,
|
||||||
},
|
},
|
||||||
@ -315,24 +315,26 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
},
|
},
|
||||||
inputWrap: {
|
inputWrap: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.surfaceElevated,
|
backgroundColor: colors.bg,
|
||||||
borderRadius: 22,
|
borderRadius: 22,
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderColor: colors.border,
|
||||||
paddingHorizontal: 14,
|
paddingHorizontal: 14,
|
||||||
minHeight: 36,
|
minHeight: 38,
|
||||||
maxHeight: 120,
|
maxHeight: 120,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
fontSize: 14,
|
fontSize: 15,
|
||||||
lineHeight: 19,
|
lineHeight: 20,
|
||||||
fontFamily: 'Nunito_400Regular',
|
fontFamily: 'Nunito_400Regular',
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
paddingVertical: Platform.OS === 'ios' ? 8 : 4,
|
paddingVertical: Platform.OS === 'ios' ? 9 : 5,
|
||||||
},
|
},
|
||||||
sendBtn: {
|
sendBtn: {
|
||||||
width: 36,
|
width: 38,
|
||||||
height: 36,
|
height: 38,
|
||||||
borderRadius: 18,
|
borderRadius: 19,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginLeft: 6,
|
marginLeft: 6,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { View, Text, Pressable, Image, StyleSheet } from 'react-native';
|
import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useColors } from '../../lib/theme';
|
import { useColors } from '../../lib/theme';
|
||||||
@ -39,25 +39,21 @@ export function RoomCard({ room, onPress }: Props) {
|
|||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable onPress={onPress} android_ripple={{ color: '#f5f5f5' }}>
|
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<View
|
<View style={[styles.avatar, { backgroundColor: room.isPublic ? '#EFF6FF' : colors.surfaceElevated }]}>
|
||||||
style={[
|
{room.avatarUrl ? (
|
||||||
styles.avatar,
|
<Image source={{ uri: room.avatarUrl }} style={styles.avatarImg} />
|
||||||
{ backgroundColor: room.isPublic ? colors.surface : colors.surfaceElevated },
|
) : !room.isPublic ? (
|
||||||
]}
|
<Text style={styles.avatarInitials}>{initials}</Text>
|
||||||
>
|
) : (
|
||||||
{room.avatarUrl ? (
|
<Ionicons name="people" size={22} color="#3B82F6" />
|
||||||
<Image source={{ uri: room.avatarUrl }} style={styles.avatarImg} />
|
)}
|
||||||
) : !room.isPublic ? (
|
</View>
|
||||||
<Text style={styles.avatarInitials}>{initials}</Text>
|
|
||||||
) : (
|
|
||||||
<Ionicons name="globe-outline" size={20} color="#007AFF" />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.info}>
|
<View style={styles.info}>
|
||||||
<View style={styles.headerRow}>
|
<View style={styles.headerRow}>
|
||||||
|
<View style={styles.nameWrap}>
|
||||||
<Text style={styles.name} numberOfLines={1}>
|
<Text style={styles.name} numberOfLines={1}>
|
||||||
{room.name}
|
{room.name}
|
||||||
</Text>
|
</Text>
|
||||||
@ -66,29 +62,32 @@ export function RoomCard({ room, onPress }: Props) {
|
|||||||
<Text style={styles.defaultBadgeText}>Standard</Text>
|
<Text style={styles.defaultBadgeText}>Standard</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
</View>
|
||||||
|
<View style={styles.metaRight}>
|
||||||
{room.lastMessage && (
|
{room.lastMessage && (
|
||||||
<Text style={styles.time}>
|
<Text style={styles.time}>
|
||||||
{formatTime(room.lastMessage.createdAt, t('chat.just_now'))}
|
{formatTime(room.lastMessage.createdAt, t('chat.just_now'))}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.footerRow}>
|
</View>
|
||||||
<View style={styles.footerTextWrap}>
|
|
||||||
{room.lastMessage ? (
|
<View style={styles.footerRow}>
|
||||||
<Text style={styles.lastMessage} numberOfLines={1}>
|
<View style={styles.footerTextWrap}>
|
||||||
<Text style={{ fontFamily: 'Nunito_700Bold' }}>
|
{room.lastMessage ? (
|
||||||
{room.lastMessage.senderName}:{' '}
|
<Text style={styles.lastMessage} numberOfLines={1}>
|
||||||
</Text>
|
<Text style={styles.senderName}>{room.lastMessage.senderName}: </Text>
|
||||||
{room.lastMessage.content}
|
{room.lastMessage.content}
|
||||||
</Text>
|
</Text>
|
||||||
) : room.description ? (
|
) : room.description ? (
|
||||||
<Text style={styles.description} numberOfLines={1}>
|
<Text style={styles.description} numberOfLines={1}>
|
||||||
{room.description}
|
{room.description}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.footerRight}>
|
||||||
<View style={styles.metaPill}>
|
<View style={styles.metaPill}>
|
||||||
<Ionicons name="people" size={11} color="#a3a3a3" />
|
<Ionicons name="people" size={10} color={colors.textMuted} />
|
||||||
<Text style={styles.memberCount}>{room.memberCount}</Text>
|
<Text style={styles.memberCount}>{room.memberCount}</Text>
|
||||||
</View>
|
</View>
|
||||||
{!room.isMember && (
|
{!room.isMember && (
|
||||||
@ -98,38 +97,40 @@ export function RoomCard({ room, onPress }: Props) {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeStyles(colors: ReturnType<typeof useColors>) {
|
function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||||
return StyleSheet.create({
|
return StyleSheet.create({
|
||||||
row: {
|
row: {
|
||||||
width: '100%',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingHorizontal: 14,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 11,
|
paddingVertical: 12,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
borderBottomColor: colors.border,
|
borderBottomColor: colors.border,
|
||||||
|
minHeight: 68,
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
width: 42,
|
width: 48,
|
||||||
height: 42,
|
height: 48,
|
||||||
borderRadius: 21,
|
borderRadius: 24,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginRight: 10,
|
marginRight: 12,
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
avatarImg: {
|
avatarImg: {
|
||||||
width: 42,
|
width: 48,
|
||||||
height: 42,
|
height: 48,
|
||||||
},
|
},
|
||||||
avatarInitials: {
|
avatarInitials: {
|
||||||
fontSize: 13,
|
fontSize: 15,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
},
|
},
|
||||||
@ -140,79 +141,93 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
headerRow: {
|
headerRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
marginBottom: 3,
|
||||||
|
},
|
||||||
|
nameWrap: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
minWidth: 0,
|
||||||
|
},
|
||||||
|
metaRight: {
|
||||||
|
marginLeft: 8,
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
footerRow: {
|
footerRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 3,
|
|
||||||
},
|
},
|
||||||
footerTextWrap: {
|
footerTextWrap: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
metaPill: {
|
footerRight: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
paddingHorizontal: 6,
|
flexShrink: 0,
|
||||||
paddingVertical: 2,
|
|
||||||
borderRadius: 8,
|
|
||||||
backgroundColor: colors.surfaceElevated,
|
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
fontSize: 14,
|
fontSize: 15,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
},
|
},
|
||||||
defaultBadge: {
|
defaultBadge: {
|
||||||
marginLeft: 6,
|
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: 6,
|
||||||
paddingVertical: 1,
|
paddingVertical: 2,
|
||||||
backgroundColor: colors.surface,
|
backgroundColor: '#EFF6FF',
|
||||||
borderRadius: 8,
|
borderRadius: 6,
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
defaultBadgeText: {
|
defaultBadgeText: {
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: '#007AFF',
|
color: '#3B82F6',
|
||||||
},
|
},
|
||||||
lastMessage: {
|
lastMessage: {
|
||||||
fontSize: 12,
|
fontSize: 13,
|
||||||
fontFamily: 'Nunito_400Regular',
|
fontFamily: 'Nunito_400Regular',
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
},
|
},
|
||||||
|
senderName: {
|
||||||
|
fontFamily: 'Nunito_600SemiBold',
|
||||||
|
color: colors.textMuted,
|
||||||
|
},
|
||||||
description: {
|
description: {
|
||||||
fontSize: 12,
|
fontSize: 13,
|
||||||
fontFamily: 'Nunito_400Regular',
|
fontFamily: 'Nunito_400Regular',
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
},
|
},
|
||||||
right: {
|
metaPill: {
|
||||||
alignItems: 'flex-end',
|
flexDirection: 'row',
|
||||||
marginLeft: 8,
|
alignItems: 'center',
|
||||||
|
gap: 3,
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
paddingVertical: 3,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: colors.surfaceElevated,
|
||||||
},
|
},
|
||||||
memberCount: {
|
memberCount: {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
marginLeft: 3,
|
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
fontSize: 10,
|
fontSize: 11,
|
||||||
fontFamily: 'Nunito_500Medium',
|
fontFamily: 'Nunito_500Medium',
|
||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
marginLeft: 'auto',
|
|
||||||
paddingLeft: 6,
|
|
||||||
},
|
},
|
||||||
joinBadge: {
|
joinBadge: {
|
||||||
marginLeft: 6,
|
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 3,
|
paddingVertical: 3,
|
||||||
backgroundColor: colors.surface,
|
backgroundColor: '#EFF6FF',
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
},
|
},
|
||||||
joinBadgeText: {
|
joinBadgeText: {
|
||||||
fontSize: 10,
|
fontSize: 11,
|
||||||
fontFamily: 'Nunito_700Bold',
|
fontFamily: 'Nunito_700Bold',
|
||||||
color: '#007AFF',
|
color: '#007AFF',
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user