diff --git a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt index 7d30da1f..b8db76b6 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.Dp import androidx.compose.ui.zIndex +import com.bitchat.android.model.BitchatMessage /** * Main ChatScreen - REFACTORED to use component-based architecture @@ -52,6 +53,7 @@ fun ChatScreen(viewModel: ChatViewModel) { var showLocationChannelsSheet by remember { mutableStateOf(false) } var showUserSheet by remember { mutableStateOf(false) } var selectedUserForSheet by remember { mutableStateOf("") } + var selectedMessageForSheet by remember { mutableStateOf(null) } var forceScrollToBottom by remember { mutableStateOf(false) } // Show password dialog when needed @@ -126,11 +128,12 @@ fun ChatScreen(viewModel: ChatViewModel) { selection = TextRange(newText.length) ) }, - onNicknameLongPress = { fullSenderName -> - // Long press - open user action sheet - // Extract base nickname from full sender name - val (baseName, _) = splitSuffix(fullSenderName) + onMessageLongPress = { message -> + // Message long press - open user action sheet with message context + // Extract base nickname from message sender (contains all necessary info) + val (baseName, _) = splitSuffix(message.sender) selectedUserForSheet = baseName + selectedMessageForSheet = message showUserSheet = true } ) @@ -261,8 +264,12 @@ fun ChatScreen(viewModel: ChatViewModel) { showLocationChannelsSheet = showLocationChannelsSheet, onLocationChannelsSheetDismiss = { showLocationChannelsSheet = false }, showUserSheet = showUserSheet, - onUserSheetDismiss = { showUserSheet = false }, + onUserSheetDismiss = { + showUserSheet = false + selectedMessageForSheet = null // Reset message when dismissing + }, selectedUserForSheet = selectedUserForSheet, + selectedMessageForSheet = selectedMessageForSheet, viewModel = viewModel ) } @@ -387,6 +394,7 @@ private fun ChatDialogs( showUserSheet: Boolean, onUserSheetDismiss: () -> Unit, selectedUserForSheet: String, + selectedMessageForSheet: BitchatMessage?, viewModel: ChatViewModel ) { // Password dialog @@ -420,6 +428,7 @@ private fun ChatDialogs( isPresented = showUserSheet, onDismiss = onUserSheetDismiss, targetNickname = selectedUserForSheet, + selectedMessage = selectedMessageForSheet, viewModel = viewModel ) } diff --git a/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt b/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt index ddaf4110..320faca0 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatUIUtils.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.unit.sp import com.bitchat.android.model.BitchatMessage import com.bitchat.android.mesh.BluetoothMeshService import androidx.compose.material3.ColorScheme +import com.bitchat.android.ui.theme.BASE_FONT_SIZE import java.text.SimpleDateFormat import java.util.* @@ -63,7 +64,7 @@ fun formatMessageAsAnnotatedString( // Sender prefix "<@" builder.pushStyle(SpanStyle( color = baseColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Medium )) builder.append("<@") @@ -72,7 +73,7 @@ fun formatMessageAsAnnotatedString( // Base name (clickable) builder.pushStyle(SpanStyle( color = baseColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Medium )) val nicknameStart = builder.length @@ -94,7 +95,7 @@ fun formatMessageAsAnnotatedString( if (suffix.isNotEmpty()) { builder.pushStyle(SpanStyle( color = baseColor.copy(alpha = 0.6f), - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Medium )) builder.append(suffix) @@ -104,7 +105,7 @@ fun formatMessageAsAnnotatedString( // Sender suffix "> " builder.pushStyle(SpanStyle( color = baseColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Medium )) builder.append("> ") @@ -116,7 +117,7 @@ fun formatMessageAsAnnotatedString( // iOS-style timestamp at the END (smaller, grey) builder.pushStyle(SpanStyle( color = Color.Gray.copy(alpha = 0.7f), - fontSize = 10.sp + fontSize = (BASE_FONT_SIZE - 4).sp )) builder.append(" [${timeFormatter.format(message.timestamp)}]") builder.pop() @@ -125,7 +126,7 @@ fun formatMessageAsAnnotatedString( // System message - iOS style builder.pushStyle(SpanStyle( color = Color.Gray, - fontSize = 12.sp, + fontSize = (BASE_FONT_SIZE - 2).sp, fontStyle = androidx.compose.ui.text.font.FontStyle.Italic )) builder.append("* ${message.content} *") @@ -134,7 +135,7 @@ fun formatMessageAsAnnotatedString( // Timestamp for system messages too builder.pushStyle(SpanStyle( color = Color.Gray.copy(alpha = 0.5f), - fontSize = 10.sp + fontSize = (BASE_FONT_SIZE - 4).sp )) builder.append(" [${timeFormatter.format(message.timestamp)}]") builder.pop() @@ -190,7 +191,7 @@ fun colorForPeerSeed(seed: String, isDark: Boolean): Color { } val saturation = if (isDark) 0.50 else 0.70 - val brightness = if (isDark) 0.95 else 0.45 + val brightness = if (isDark) 0.85 else 0.35 return Color.hsv( hue = (hue * 360).toFloat(), @@ -269,7 +270,7 @@ private fun appendIOSFormattedContent( if (beforeText.isNotEmpty()) { builder.pushStyle(SpanStyle( color = baseColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Normal )) if (isMentioned) { @@ -299,7 +300,7 @@ private fun appendIOSFormattedContent( // "@" symbol builder.pushStyle(SpanStyle( color = mentionColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.SemiBold )) builder.append("@") @@ -308,7 +309,7 @@ private fun appendIOSFormattedContent( // Base name builder.pushStyle(SpanStyle( color = mentionColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.SemiBold )) builder.append(mBase) @@ -318,7 +319,7 @@ private fun appendIOSFormattedContent( if (mSuffix.isNotEmpty()) { builder.pushStyle(SpanStyle( color = mentionColor.copy(alpha = 0.6f), - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.SemiBold )) builder.append(mSuffix) @@ -329,7 +330,7 @@ private fun appendIOSFormattedContent( // iOS-style: render hashtags like normal content (no special styling) builder.pushStyle(SpanStyle( color = baseColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Normal )) if (isMentioned) { @@ -351,7 +352,7 @@ private fun appendIOSFormattedContent( val remainingText = content.substring(lastEnd) builder.pushStyle(SpanStyle( color = baseColor, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Normal )) if (isMentioned) { diff --git a/app/src/main/java/com/bitchat/android/ui/ChatUserSheet.kt b/app/src/main/java/com/bitchat/android/ui/ChatUserSheet.kt index 9ac8c11d..e1f5df5e 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatUserSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatUserSheet.kt @@ -11,7 +11,11 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bitchat.android.ui.theme.BASE_FONT_SIZE +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString import kotlinx.coroutines.launch +import com.bitchat.android.model.BitchatMessage /** * User Action Sheet for selecting actions on a specific user (slap, hug, block) @@ -23,10 +27,12 @@ fun ChatUserSheet( isPresented: Boolean, onDismiss: () -> Unit, targetNickname: String, + selectedMessage: BitchatMessage? = null, viewModel: ChatViewModel, modifier: Modifier = Modifier ) { val coroutineScope = rememberCoroutineScope() + val clipboardManager = LocalClipboardManager.current // Bottom sheet state val sheetState = rememberModalBottomSheetState( @@ -39,6 +45,7 @@ fun ChatUserSheet( val standardGreen = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D) // iOS green val standardBlue = Color(0xFF007AFF) // iOS blue val standardRed = Color(0xFFFF3B30) // iOS red + val standardGrey = if (isDark) Color(0xFF8E8E93) else Color(0xFF6D6D70) // iOS grey if (isPresented) { ModalBottomSheet( @@ -62,7 +69,7 @@ fun ChatUserSheet( ) Text( - text = "choose an action for this user", + text = if (selectedMessage != null) "choose an action for this message or user" else "choose an action for this user", fontSize = 12.sp, fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) @@ -72,53 +79,72 @@ fun ChatUserSheet( LazyColumn( modifier = Modifier.fillMaxWidth() ) { - // Slap action - item { - UserActionRow( - title = "slap $targetNickname", - subtitle = "send a playful slap message", - titleColor = standardBlue, - onClick = { - // Send slap command - viewModel.sendMessage("/slap $targetNickname") - onDismiss() - } - ) - } - - // Hug action - item { - UserActionRow( - title = "hug $targetNickname", - subtitle = "send a friendly hug message", - titleColor = standardGreen, - onClick = { - // Send hug command - viewModel.sendMessage("/hug $targetNickname") - onDismiss() - } - ) - } - - // Block action - item { - UserActionRow( - title = "block $targetNickname", - subtitle = "block all messages from this user", - titleColor = standardRed, - onClick = { - // Check if we're in a geohash channel - val selectedLocationChannel = viewModel.selectedLocationChannel.value - if (selectedLocationChannel is com.bitchat.android.geohash.ChannelID.Location) { - // Get user's nostr public key and add to geohash block list - viewModel.blockUserInGeohash(targetNickname) - } else { - // Regular mesh blocking - viewModel.sendMessage("/block $targetNickname") + // Copy message action (only show if we have a message) + selectedMessage?.let { message -> + item { + UserActionRow( + title = "copy message", + subtitle = "copy this message to clipboard", + titleColor = standardGrey, + onClick = { + // Copy the message content to clipboard + clipboardManager.setText(AnnotatedString(message.content)) + onDismiss() } - onDismiss() - } - ) + ) + } + } + + // Only show user actions for other users' messages or when no message is selected + if (selectedMessage?.sender != viewModel.nickname.value) { + // Slap action + item { + UserActionRow( + title = "slap $targetNickname", + subtitle = "send a playful slap message", + titleColor = standardBlue, + onClick = { + // Send slap command + viewModel.sendMessage("/slap $targetNickname") + onDismiss() + } + ) + } + + // Hug action + item { + UserActionRow( + title = "hug $targetNickname", + subtitle = "send a friendly hug message", + titleColor = standardGreen, + onClick = { + // Send hug command + viewModel.sendMessage("/hug $targetNickname") + onDismiss() + } + ) + } + + // Block action + item { + UserActionRow( + title = "block $targetNickname", + subtitle = "block all messages from this user", + titleColor = standardRed, + onClick = { + // Check if we're in a geohash channel + val selectedLocationChannel = viewModel.selectedLocationChannel.value + if (selectedLocationChannel is com.bitchat.android.geohash.ChannelID.Location) { + // Get user's nostr public key and add to geohash block list + viewModel.blockUserInGeohash(targetNickname) + } else { + // Regular mesh blocking + viewModel.sendMessage("/block $targetNickname") + } + onDismiss() + } + ) + } } } @@ -133,7 +159,7 @@ fun ChatUserSheet( ) { Text( text = "cancel", - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace ) } @@ -164,7 +190,7 @@ private fun UserActionRow( ) { Text( text = title, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Medium, color = titleColor diff --git a/app/src/main/java/com/bitchat/android/ui/GeohashPeopleList.kt b/app/src/main/java/com/bitchat/android/ui/GeohashPeopleList.kt index 1f7111ef..70e159f8 100644 --- a/app/src/main/java/com/bitchat/android/ui/GeohashPeopleList.kt +++ b/app/src/main/java/com/bitchat/android/ui/GeohashPeopleList.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bitchat.android.ui.theme.BASE_FONT_SIZE import java.util.* /** @@ -77,7 +78,7 @@ fun GeohashPeopleList( text = "nobody around...", style = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, - fontSize = 14.sp + fontSize = BASE_FONT_SIZE.sp ), color = colorScheme.onSurface.copy(alpha = 0.5f), modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp) @@ -212,7 +213,7 @@ private fun GeohashPersonItem( text = baseName, style = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isMe) FontWeight.Bold else FontWeight.Normal ), color = baseColor @@ -224,7 +225,7 @@ private fun GeohashPersonItem( text = suffix, style = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, - fontSize = 14.sp + fontSize = BASE_FONT_SIZE.sp ), color = baseColor.copy(alpha = 0.6f) ) @@ -236,7 +237,7 @@ private fun GeohashPersonItem( text = " (you)", style = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, - fontSize = 14.sp + fontSize = BASE_FONT_SIZE.sp ), color = baseColor ) diff --git a/app/src/main/java/com/bitchat/android/ui/InputComponents.kt b/app/src/main/java/com/bitchat/android/ui/InputComponents.kt index 9cee91f1..ba45f9f8 100644 --- a/app/src/main/java/com/bitchat/android/ui/InputComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/InputComponents.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.unit.sp import com.bitchat.android.R import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.text.withStyle +import com.bitchat.android.ui.theme.BASE_FONT_SIZE /** * Input components for ChatScreen @@ -326,7 +327,7 @@ fun CommandSuggestionItem( fontWeight = FontWeight.Medium ), color = colorScheme.primary, - fontSize = 11.sp + fontSize = (BASE_FONT_SIZE - 4).sp ) // Show syntax if any @@ -337,7 +338,7 @@ fun CommandSuggestionItem( fontFamily = FontFamily.Monospace ), color = colorScheme.onSurface.copy(alpha = 0.8f), - fontSize = 10.sp + fontSize = (BASE_FONT_SIZE - 5).sp ) } @@ -348,7 +349,7 @@ fun CommandSuggestionItem( fontFamily = FontFamily.Monospace ), color = colorScheme.onSurface.copy(alpha = 0.7f), - fontSize = 10.sp, + fontSize = (BASE_FONT_SIZE - 5).sp, maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -400,7 +401,7 @@ fun MentionSuggestionItem( fontWeight = FontWeight.SemiBold ), color = Color(0xFFFF9500), // Orange like mentions - fontSize = 11.sp + fontSize = (BASE_FONT_SIZE - 4).sp ) Spacer(modifier = Modifier.weight(1f)) @@ -411,7 +412,7 @@ fun MentionSuggestionItem( fontFamily = FontFamily.Monospace ), color = colorScheme.onSurface.copy(alpha = 0.7f), - fontSize = 10.sp + fontSize = (BASE_FONT_SIZE - 5).sp ) } } diff --git a/app/src/main/java/com/bitchat/android/ui/LinkPreviewPill.kt b/app/src/main/java/com/bitchat/android/ui/LinkPreviewPill.kt index 4a7d1dd0..25d00bd7 100644 --- a/app/src/main/java/com/bitchat/android/ui/LinkPreviewPill.kt +++ b/app/src/main/java/com/bitchat/android/ui/LinkPreviewPill.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bitchat.android.ui.theme.BASE_FONT_SIZE import java.net.URL /** @@ -109,7 +110,7 @@ fun LinkPreviewPill( Text( text = displayTitle, fontFamily = FontFamily.Monospace, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = FontWeight.SemiBold, color = textColor, maxLines = 2, diff --git a/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt b/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt index 30289e0a..1539793e 100644 --- a/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/LocationChannelsSheet.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bitchat.android.ui.theme.BASE_FONT_SIZE import kotlinx.coroutines.launch import com.bitchat.android.geohash.ChannelID import com.bitchat.android.geohash.GeohashChannel @@ -240,7 +241,7 @@ fun LocationChannelsSheet( ) { Text( text = "#", - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) @@ -260,7 +261,7 @@ fun LocationChannelsSheet( customError = null }, textStyle = androidx.compose.ui.text.TextStyle( - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface ), @@ -283,7 +284,7 @@ fun LocationChannelsSheet( if (customGeohash.isEmpty()) { Text( text = "geohash", - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) ) @@ -321,7 +322,7 @@ fun LocationChannelsSheet( ) { Text( text = "teleport", - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace ) // iOS has a face.dashed icon, use closest Material equivalent @@ -446,7 +447,7 @@ private fun ChannelRow( Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { Text( text = baseTitle, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontFamily = FontFamily.Monospace, fontWeight = if (titleBold) FontWeight.Bold else FontWeight.Normal, color = titleColor ?: MaterialTheme.colorScheme.onSurface diff --git a/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt b/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt index 1dbbfe55..684d9a3f 100644 --- a/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/MessageComponents.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.ClickableText -import androidx.compose.foundation.text.selection.SelectionContainer + import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -37,7 +37,7 @@ fun MessagesList( modifier: Modifier = Modifier, forceScrollToBottom: Boolean = false, onNicknameClick: ((String) -> Unit)? = null, - onNicknameLongPress: ((String) -> Unit)? = null + onMessageLongPress: ((BitchatMessage) -> Unit)? = null ) { val listState = rememberLazyListState() @@ -48,15 +48,14 @@ fun MessagesList( LaunchedEffect(messages.size) { if (messages.isNotEmpty()) { val layoutInfo = listState.layoutInfo - val lastVisibleIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1 - val totalItems = layoutInfo.totalItemsCount + val firstVisibleIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: -1 - // Always scroll to bottom on first load, or when user is near the bottom + // With reverseLayout=true and reversed data, index 0 is the latest message at the bottom val isFirstLoad = !hasScrolledToInitialPosition - val isNearBottom = lastVisibleIndex >= totalItems - 3 + val isNearLatest = firstVisibleIndex <= 2 - if (isFirstLoad || isNearBottom) { - listState.animateScrollToItem(messages.size - 1) + if (isFirstLoad || isNearLatest) { + listState.animateScrollToItem(0) if (isFirstLoad) { hasScrolledToInitialPosition = true } @@ -67,25 +66,26 @@ fun MessagesList( // Force scroll to bottom when requested (e.g., when user sends a message) LaunchedEffect(forceScrollToBottom) { if (forceScrollToBottom && messages.isNotEmpty()) { - listState.animateScrollToItem(messages.size - 1) + // With reverseLayout=true and reversed data, latest is at index 0 + listState.animateScrollToItem(0) } } - SelectionContainer(modifier = modifier) { - LazyColumn( - state = listState, - contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - items(messages) { message -> + LazyColumn( + state = listState, + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = modifier, + reverseLayout = true + ) { + items(messages.asReversed()) { message -> MessageItem( message = message, currentUserNickname = currentUserNickname, meshService = meshService, onNicknameClick = onNicknameClick, - onNicknameLongPress = onNicknameLongPress + onMessageLongPress = onMessageLongPress ) - } } } } @@ -97,7 +97,7 @@ fun MessageItem( currentUserNickname: String, meshService: BluetoothMeshService, onNicknameClick: ((String) -> Unit)? = null, - onNicknameLongPress: ((String) -> Unit)? = null + onMessageLongPress: ((BitchatMessage) -> Unit)? = null ) { val colorScheme = MaterialTheme.colorScheme val timeFormatter = remember { SimpleDateFormat("HH:mm:ss", Locale.getDefault()) } @@ -119,7 +119,7 @@ fun MessageItem( colorScheme = colorScheme, timeFormatter = timeFormatter, onNicknameClick = onNicknameClick, - onNicknameLongPress = onNicknameLongPress, + onMessageLongPress = onMessageLongPress, modifier = Modifier.weight(1f) ) @@ -164,7 +164,7 @@ private fun MessageTextWithClickableNicknames( colorScheme: ColorScheme, timeFormatter: SimpleDateFormat, onNicknameClick: ((String) -> Unit)?, - onNicknameLongPress: ((String) -> Unit)?, + onMessageLongPress: ((BitchatMessage) -> Unit)?, modifier: Modifier = Modifier ) { val annotatedText = formatMessageAsAnnotatedString( @@ -180,8 +180,8 @@ private fun MessageTextWithClickableNicknames( message.sender == currentUserNickname || message.sender.startsWith("$currentUserNickname#") - if (!isSelf && (onNicknameClick != null || onNicknameLongPress != null)) { - // Use Text with combinedClickable for nickname interactions + if (!isSelf && (onNicknameClick != null || onMessageLongPress != null)) { + // Use Text with combinedClickable for nickname interactions and message long press Text( text = annotatedText, modifier = modifier.combinedClickable( @@ -198,16 +198,8 @@ private fun MessageTextWithClickableNicknames( } }, onLongClick = { - // Handle long press for the first nickname - val nicknameAnnotations = annotatedText.getStringAnnotations( - tag = "nickname_click", - start = 0, - end = annotatedText.length - ) - if (nicknameAnnotations.isNotEmpty()) { - val nickname = nicknameAnnotations.first().item - onNicknameLongPress?.invoke(nickname) - } + // Always use message long press - contains all necessary information + onMessageLongPress?.invoke(message) } ), fontFamily = FontFamily.Monospace, @@ -218,19 +210,24 @@ private fun MessageTextWithClickableNicknames( ) ) } else { - // Use selectable text when no interactions needed - SelectionContainer { - Text( - text = annotatedText, - modifier = modifier, - fontFamily = FontFamily.Monospace, - softWrap = true, - overflow = TextOverflow.Visible, - style = androidx.compose.ui.text.TextStyle( - color = colorScheme.onSurface + // Use regular text with message long press support for own messages + Text( + text = annotatedText, + modifier = if (onMessageLongPress != null) { + modifier.combinedClickable( + onClick = { /* No action for own messages */ }, + onLongClick = { onMessageLongPress.invoke(message) } ) + } else { + modifier + }, + fontFamily = FontFamily.Monospace, + softWrap = true, + overflow = TextOverflow.Visible, + style = androidx.compose.ui.text.TextStyle( + color = colorScheme.onSurface ) - } + ) } } diff --git a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt index 7cf55de8..946a4d65 100644 --- a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bitchat.android.ui.theme.BASE_FONT_SIZE /** @@ -400,7 +401,7 @@ private fun PeerItem( text = baseName, style = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, - fontSize = 14.sp, + fontSize = BASE_FONT_SIZE.sp, fontWeight = if (isMe) FontWeight.Bold else FontWeight.Normal ), color = baseColor @@ -412,7 +413,7 @@ private fun PeerItem( text = suffix, style = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, - fontSize = 14.sp + fontSize = BASE_FONT_SIZE.sp ), color = baseColor.copy(alpha = 0.6f) ) diff --git a/app/src/main/java/com/bitchat/android/ui/theme/Typography.kt b/app/src/main/java/com/bitchat/android/ui/theme/Typography.kt index 95aa8fbc..04a0b14c 100644 --- a/app/src/main/java/com/bitchat/android/ui/theme/Typography.kt +++ b/app/src/main/java/com/bitchat/android/ui/theme/Typography.kt @@ -6,48 +6,51 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp -// Typography matching the iOS monospace design - increased font sizes for better readability +// Base font size for consistent scaling across the app +const val BASE_FONT_SIZE = 15 // sp - increased from 14sp for better readability + +// Typography matching the iOS monospace design - using BASE_FONT_SIZE for consistency val Typography = Typography( bodyLarge = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 22.sp + fontSize = (BASE_FONT_SIZE + 1).sp, + lineHeight = (BASE_FONT_SIZE + 7).sp ), bodyMedium = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Normal, - fontSize = 14.sp, - lineHeight = 18.sp + fontSize = BASE_FONT_SIZE.sp, + lineHeight = (BASE_FONT_SIZE + 3).sp ), bodySmall = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Normal, - fontSize = 12.sp, - lineHeight = 16.sp + fontSize = (BASE_FONT_SIZE - 3).sp, + lineHeight = (BASE_FONT_SIZE + 1).sp ), headlineSmall = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Medium, - fontSize = 18.sp, - lineHeight = 24.sp + fontSize = (BASE_FONT_SIZE + 3).sp, + lineHeight = (BASE_FONT_SIZE + 9).sp ), titleMedium = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Medium, - fontSize = 16.sp, - lineHeight = 22.sp + fontSize = (BASE_FONT_SIZE + 1).sp, + lineHeight = (BASE_FONT_SIZE + 7).sp ), labelMedium = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Medium, - fontSize = 13.sp, - lineHeight = 18.sp + fontSize = (BASE_FONT_SIZE - 2).sp, + lineHeight = (BASE_FONT_SIZE + 3).sp ), labelSmall = TextStyle( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Normal, - fontSize = 11.sp, - lineHeight = 16.sp + fontSize = (BASE_FONT_SIZE - 4).sp, + lineHeight = (BASE_FONT_SIZE + 1).sp ) )