mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 23:45:19 +00:00
slightly increase padding (#295)
* slightly increase padding * better colors * add copy to bottom sheet * increase font size * base font size * anchor chat at the bottom
This commit is contained in:
@@ -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<BitchatMessage?>(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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user