diff --git a/apps/rebreak-native/app/(app)/chat.tsx b/apps/rebreak-native/app/(app)/chat.tsx index a257be1..6e7c45a 100644 --- a/apps/rebreak-native/app/(app)/chat.tsx +++ b/apps/rebreak-native/app/(app)/chat.tsx @@ -3,7 +3,6 @@ import { View, Text, FlatList, - Pressable, TouchableOpacity, ActivityIndicator, Image, @@ -45,51 +44,51 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void } const hasUnread = conv.unreadCount > 0; return ( - + - - {conv.partnerAvatar ? ( - - ) : ( - - {conv.partnerName.slice(0, 2).toUpperCase()} - + + {conv.partnerAvatar ? ( + + ) : ( + + {conv.partnerName.slice(0, 2).toUpperCase()} + + )} + + + + + {conv.partnerName} + + + {formatTime(conv.lastMessageAt, t('chat.just_now'))} + + + + + {conv.isOwn ? `${t('chat.you')} ` : ''} + {conv.lastMessage} + + {hasUnread && ( + + + {conv.unreadCount > 99 ? '99+' : conv.unreadCount} + + )} - - - - {conv.partnerName} - - - {formatTime(conv.lastMessageAt, t('chat.just_now'))} - - - - - {conv.isOwn ? t('chat.you') : ''} - {conv.lastMessage} - - {hasUnread && ( - - {conv.unreadCount} - - )} - - + - + ); } @@ -352,28 +351,29 @@ function makeStyles(colors: ReturnType) { marginTop: 12, }, dmRow: { - width: '100%', flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 14, - paddingVertical: 11, + paddingHorizontal: 16, + paddingVertical: 12, backgroundColor: colors.bg, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border, + minHeight: 68, }, dmAvatar: { - width: 42, - height: 42, - borderRadius: 21, + width: 48, + height: 48, + borderRadius: 24, backgroundColor: colors.surfaceElevated, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', - marginRight: 10, + marginRight: 12, + flexShrink: 0, }, - dmAvatarImg: { width: 42, height: 42 }, + dmAvatarImg: { width: 48, height: 48 }, dmAvatarInitials: { - fontSize: 13, + fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.textMuted, }, @@ -384,13 +384,13 @@ function makeStyles(colors: ReturnType) { justifyContent: 'space-between', }, dmName: { - fontSize: 14, + fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.text, flexShrink: 1, marginRight: 6, }, - dmTime: { fontSize: 11, fontFamily: 'Nunito_600SemiBold' }, + dmTime: { fontSize: 11, fontFamily: 'Nunito_500Medium' }, dmBottomRow: { flexDirection: 'row', alignItems: 'center', diff --git a/apps/rebreak-native/components/chat/ChatBubble.tsx b/apps/rebreak-native/components/chat/ChatBubble.tsx index 2315da0..e3a7790 100644 --- a/apps/rebreak-native/components/chat/ChatBubble.tsx +++ b/apps/rebreak-native/components/chat/ChatBubble.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from 'react'; +import { useState } from 'react'; import { View, Text, @@ -7,7 +7,6 @@ import { Image, StyleSheet, Modal, - Alert, Platform, } from 'react-native'; import * as Clipboard from 'expo-clipboard'; @@ -68,7 +67,6 @@ export function ChatBubble({ const colors = useColors(); const styles = makeStyles(colors); const [actionsOpen, setActionsOpen] = useState(false); - const longPressTimer = useRef | null>(null); const isImageOnly = !!msg.attachmentUrl && msg.attachmentType === 'image' && !msg.content && !msg.replyTo; @@ -234,24 +232,25 @@ export function ChatBubble({ {msg.content} )} - {/* Footer */} + {/* Footer: timestamp + read-receipt inline below content */} {!isImageOnly && ( - + {msg.likesCount > 0 && ( - - + + {msg.likesCount} @@ -260,8 +259,9 @@ export function ChatBubble({ )} {formatTime(msg.createdAt)} @@ -269,9 +269,9 @@ export function ChatBubble({ {msg.isOwn && !hideReadStatus && ( )} @@ -335,36 +335,37 @@ function makeStyles(colors: ReturnType) { return StyleSheet.create({ row: { flexDirection: 'row', - paddingHorizontal: 8, + paddingHorizontal: 10, }, avatarSlot: { - width: 30, - marginRight: 4, + width: 32, + marginRight: 6, justifyContent: 'flex-end', }, avatar: { - width: 26, - height: 26, - borderRadius: 13, + width: 28, + height: 28, + borderRadius: 14, backgroundColor: colors.surfaceElevated, }, bubbleCol: { - maxWidth: '78%', + maxWidth: '76%', }, nickname: { - fontSize: 10, + fontSize: 11, fontFamily: 'Nunito_700Bold', color: '#007AFF', - marginBottom: 2, - marginLeft: 10, + marginBottom: 3, + marginLeft: 12, }, bubble: { - borderRadius: 18, - paddingHorizontal: 12, - paddingVertical: 6, + borderRadius: 20, + paddingHorizontal: 14, + paddingTop: 8, + paddingBottom: 6, shadowColor: '#000', - shadowOpacity: 0.05, - shadowRadius: 1, + shadowOpacity: 0.06, + shadowRadius: 2, shadowOffset: { width: 0, height: 1 }, }, bubbleOwn: { @@ -380,10 +381,10 @@ function makeStyles(colors: ReturnType) { borderRadius: 8, paddingHorizontal: 8, paddingVertical: 4, - marginBottom: 4, + marginBottom: 6, }, imageWrap: { - borderRadius: 12, + borderRadius: 14, overflow: 'hidden', position: 'relative', }, @@ -405,27 +406,26 @@ function makeStyles(colors: ReturnType) { }, content: { fontSize: 14, - lineHeight: 20, + lineHeight: 21, fontFamily: 'Nunito_400Regular', }, footer: { - position: 'absolute', - bottom: 4, - right: 8, flexDirection: 'row', alignItems: 'center', + gap: 3, + marginTop: 4, }, sheetBackdrop: { flex: 1, - backgroundColor: 'rgba(0,0,0,0.5)', + backgroundColor: 'rgba(0,0,0,0.35)', justifyContent: 'flex-end', }, sheet: { backgroundColor: colors.bg, - borderTopLeftRadius: 20, - borderTopRightRadius: 20, + borderTopLeftRadius: 22, + borderTopRightRadius: 22, padding: 8, - paddingBottom: Platform.OS === 'ios' ? 32 : 16, + paddingBottom: Platform.OS === 'ios' ? 34 : 16, }, sheetGrabber: { width: 36, diff --git a/apps/rebreak-native/components/chat/ChatInput.tsx b/apps/rebreak-native/components/chat/ChatInput.tsx index 63c5e9a..ae354d6 100644 --- a/apps/rebreak-native/components/chat/ChatInput.tsx +++ b/apps/rebreak-native/components/chat/ChatInput.tsx @@ -240,7 +240,7 @@ function decodeBase64(base64: string): Uint8Array { function makeStyles(colors: ReturnType) { return StyleSheet.create({ container: { - backgroundColor: colors.bg, + backgroundColor: colors.surface, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: colors.border, }, @@ -315,24 +315,26 @@ function makeStyles(colors: ReturnType) { }, inputWrap: { flex: 1, - backgroundColor: colors.surfaceElevated, + backgroundColor: colors.bg, borderRadius: 22, + borderWidth: StyleSheet.hairlineWidth, + borderColor: colors.border, paddingHorizontal: 14, - minHeight: 36, + minHeight: 38, maxHeight: 120, justifyContent: 'center', }, input: { - fontSize: 14, - lineHeight: 19, + fontSize: 15, + lineHeight: 20, fontFamily: 'Nunito_400Regular', color: colors.text, - paddingVertical: Platform.OS === 'ios' ? 8 : 4, + paddingVertical: Platform.OS === 'ios' ? 9 : 5, }, sendBtn: { - width: 36, - height: 36, - borderRadius: 18, + width: 38, + height: 38, + borderRadius: 19, alignItems: 'center', justifyContent: 'center', marginLeft: 6, diff --git a/apps/rebreak-native/components/chat/RoomCard.tsx b/apps/rebreak-native/components/chat/RoomCard.tsx index ef7d66f..44e2e4a 100644 --- a/apps/rebreak-native/components/chat/RoomCard.tsx +++ b/apps/rebreak-native/components/chat/RoomCard.tsx @@ -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 { useTranslation } from 'react-i18next'; import { useColors } from '../../lib/theme'; @@ -39,25 +39,21 @@ export function RoomCard({ room, onPress }: Props) { .join(''); return ( - + - - {room.avatarUrl ? ( - - ) : !room.isPublic ? ( - {initials} - ) : ( - - )} - + + {room.avatarUrl ? ( + + ) : !room.isPublic ? ( + {initials} + ) : ( + + )} + - - + + + {room.name} @@ -66,29 +62,32 @@ export function RoomCard({ room, onPress }: Props) { Standard )} + + {room.lastMessage && ( {formatTime(room.lastMessage.createdAt, t('chat.just_now'))} )} - - - {room.lastMessage ? ( - - - {room.lastMessage.senderName}:{' '} - - {room.lastMessage.content} - - ) : room.description ? ( - - {room.description} - - ) : null} - + + + + + {room.lastMessage ? ( + + {room.lastMessage.senderName}: + {room.lastMessage.content} + + ) : room.description ? ( + + {room.description} + + ) : null} + + - + {room.memberCount} {!room.isMember && ( @@ -98,38 +97,40 @@ export function RoomCard({ room, onPress }: Props) { )} + - + ); } function makeStyles(colors: ReturnType) { return StyleSheet.create({ row: { - width: '100%', flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 14, - paddingVertical: 11, + paddingHorizontal: 16, + paddingVertical: 12, backgroundColor: colors.bg, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border, + minHeight: 68, }, avatar: { - width: 42, - height: 42, - borderRadius: 21, + width: 48, + height: 48, + borderRadius: 24, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', - marginRight: 10, + marginRight: 12, + flexShrink: 0, }, avatarImg: { - width: 42, - height: 42, + width: 48, + height: 48, }, avatarInitials: { - fontSize: 13, + fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.textMuted, }, @@ -140,79 +141,93 @@ function makeStyles(colors: ReturnType) { headerRow: { flexDirection: 'row', alignItems: 'center', + marginBottom: 3, + }, + nameWrap: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + gap: 6, + minWidth: 0, + }, + metaRight: { + marginLeft: 8, + flexShrink: 0, }, footerRow: { flexDirection: 'row', alignItems: 'center', - marginTop: 3, }, footerTextWrap: { flex: 1, minWidth: 0, }, - metaPill: { + footerRight: { flexDirection: 'row', alignItems: 'center', + gap: 6, marginLeft: 8, - paddingHorizontal: 6, - paddingVertical: 2, - borderRadius: 8, - backgroundColor: colors.surfaceElevated, + flexShrink: 0, }, name: { - fontSize: 14, + fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.text, flexShrink: 1, }, defaultBadge: { - marginLeft: 6, paddingHorizontal: 6, - paddingVertical: 1, - backgroundColor: colors.surface, - borderRadius: 8, + paddingVertical: 2, + backgroundColor: '#EFF6FF', + borderRadius: 6, + flexShrink: 0, }, defaultBadgeText: { fontSize: 9, fontFamily: 'Nunito_700Bold', - color: '#007AFF', + color: '#3B82F6', }, lastMessage: { - fontSize: 12, + fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, }, + senderName: { + fontFamily: 'Nunito_600SemiBold', + color: colors.textMuted, + }, description: { - fontSize: 12, + fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, }, - right: { - alignItems: 'flex-end', - marginLeft: 8, + metaPill: { + flexDirection: 'row', + alignItems: 'center', + gap: 3, + paddingHorizontal: 6, + paddingVertical: 3, + borderRadius: 8, + backgroundColor: colors.surfaceElevated, }, memberCount: { fontSize: 11, fontFamily: 'Nunito_700Bold', color: colors.textMuted, - marginLeft: 3, }, time: { - fontSize: 10, + fontSize: 11, fontFamily: 'Nunito_500Medium', color: colors.textMuted, - marginLeft: 'auto', - paddingLeft: 6, }, joinBadge: { - marginLeft: 6, paddingHorizontal: 8, paddingVertical: 3, - backgroundColor: colors.surface, + backgroundColor: '#EFF6FF', borderRadius: 10, }, joinBadgeText: { - fontSize: 10, + fontSize: 11, fontFamily: 'Nunito_700Bold', color: '#007AFF', },