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:
callebtc
2025-08-23 14:14:42 +02:00
committed by GitHub
parent 26520991cf
commit a7604d9026
10 changed files with 185 additions and 144 deletions
@@ -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
)
}
)
}
}