android: shrink composer pill; float it over the message list
- reduce composer size (padding, corner radius, send button 40->36dp) - move composer plane (back-to-bottom button, attachments, banners, slash drawer, input pill) into a bottom overlay so messages scroll behind it; overlay height drives the list's bottom content padding
This commit is contained in:
1 parent
7f0bdcbbc1
commit
8fba00ba7f
1 file changed
+46
-12
@@ -69,6 +69,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -91,6 +92,7 @@ import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
@@ -474,14 +476,26 @@ fun ChatScreen(controller: IrisController) {
|
||||
)
|
||||
}
|
||||
|
||||
// Messages (M7: interleaved date separators; bubbles capped at 85% width)
|
||||
// Messages (M7: interleaved date separators; bubbles capped at 85% width).
|
||||
// The composer plane (back-to-bottom button, attachments, banners, slash
|
||||
// drawer, input pill) is an OVERLAY at the bottom of this area: the list
|
||||
// scrolls behind it, so the pill floats over the wallpaper instead of
|
||||
// sitting in a hard-edged gap. The overlay's measured height becomes the
|
||||
// list's bottom content padding, keeping the newest message visible above it.
|
||||
var overlayHeightPx by remember { mutableIntStateOf(0) }
|
||||
val rows = remember(items) { buildChatRows(items) }
|
||||
BoxWithConstraints(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
||||
val bubbleMaxWidth = (maxWidth.value * 0.85f).dp
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
contentPadding =
|
||||
PaddingValues(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = (overlayHeightPx / LocalDensity.current.density).dp + 16.dp,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
if (items.isEmpty() && !typing) {
|
||||
@@ -552,17 +566,27 @@ fun ChatScreen(controller: IrisController) {
|
||||
}
|
||||
}
|
||||
|
||||
// Composer plane (overlay): back-to-bottom button, attachments, banners,
|
||||
// slash drawer and the input pill. The list scrolls behind it; its
|
||||
// measured height is the list's bottom content padding (above).
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.onSizeChanged { overlayHeightPx = it.height },
|
||||
) {
|
||||
// Back-to-bottom button: appears once the user has scrolled up
|
||||
// to read history; a tap glides the view back to the latest
|
||||
// message (smooth but quick).
|
||||
if (!isAtBottom && !isAutoScrolling) {
|
||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
|
||||
val alpha = remember { Animatable(0f) }
|
||||
LaunchedEffect(Unit) { alpha.animateTo(1f, tween(150)) }
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(end = 16.dp, bottom = 12.dp)
|
||||
.padding(end = 16.dp, bottom = 8.dp)
|
||||
.alpha(alpha.value)
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
@@ -777,17 +801,17 @@ fun ChatScreen(controller: IrisController) {
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.clip(RoundedCornerShape(28.dp))
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(IrisColors.surface)
|
||||
.padding(horizontal = 6.dp, vertical = 4.dp),
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 6.dp, vertical = 10.dp),
|
||||
.padding(horizontal = 6.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
@@ -797,8 +821,16 @@ fun ChatScreen(controller: IrisController) {
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = if (textHeightPx > 0f) (textHeightPx / layoutDensity).dp else Dp.Unspecified)
|
||||
.focusRequester(composerFocusRequester)
|
||||
.heightIn(
|
||||
max =
|
||||
if (textHeightPx >
|
||||
0f
|
||||
) {
|
||||
(textHeightPx / layoutDensity).dp
|
||||
} else {
|
||||
Dp.Unspecified
|
||||
},
|
||||
).focusRequester(composerFocusRequester)
|
||||
.onFocusChanged { composerFocused = it.isFocused },
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(color = IrisColors.onSurface),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
|
||||
@@ -827,13 +859,15 @@ fun ChatScreen(controller: IrisController) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.size(40.dp)
|
||||
.size(36.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (canSend) IrisColors.primary else IrisColors.primary.copy(alpha = 0.35f))
|
||||
.clickable(enabled = canSend) { doSend() },
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text("➤", color = Color.White, fontSize = 16.sp)
|
||||
Text("➤", color = Color.White, fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user