android: fix voice recording composer overlap
Some checks failed
Android CI / android (push) Has started running
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
Codex
2026-03-10 08:54:34 +03:00
parent f7ef10b011
commit 22ee59fd74
2 changed files with 136 additions and 94 deletions

View File

@@ -967,3 +967,10 @@
- Added dedicated read-only channel bottom bar (for non owner/admin): - Added dedicated read-only channel bottom bar (for non owner/admin):
- compact Telegram-like controls with search + centered `Включить звук` action + notifications icon. - compact Telegram-like controls with search + centered `Включить звук` action + notifications icon.
- Kept existing full composer for roles allowed to post in channels (owner/admin). - Kept existing full composer for roles allowed to post in channels (owner/admin).
### Step 132 - Voice recording composer overlap fix
- Fixed composer overlap during voice recording:
- recording status/hint is now rendered in a dedicated top block inside composer,
- formatting toolbar is hidden while recording is active.
- Prevented controls collision for locked-recording actions:
- `Cancel/Send` now render on a separate row in locked state.

View File

@@ -1114,6 +1114,15 @@ fun ChatScreen(
.padding(horizontal = 8.dp, vertical = 6.dp), .padding(horizontal = 8.dp, vertical = 6.dp),
verticalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp),
) { ) {
if (state.isRecordingVoice) {
VoiceRecordingStatusRow(
durationMs = state.voiceRecordingDurationMs,
hint = state.voiceRecordingHint,
isLocked = state.isVoiceLocked,
onCancel = onVoiceRecordCancel,
onSend = onVoiceRecordSend,
)
} else {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1185,6 +1194,7 @@ fun ChatScreen(
enabled = state.canSendMessages, enabled = state.canSendMessages,
) { Icon(Icons.Filled.Link, contentDescription = "Link") } ) { Icon(Icons.Filled.Link, contentDescription = "Link") }
} }
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -1267,30 +1277,6 @@ fun ChatScreen(
} }
} }
} }
if (state.isRecordingVoice) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Voice ${formatDuration(state.voiceRecordingDurationMs.toInt())}",
style = MaterialTheme.typography.labelMedium,
)
Text(
text = state.voiceRecordingHint ?: "",
style = MaterialTheme.typography.labelSmall,
)
if (state.isVoiceLocked) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Button(onClick = onVoiceRecordCancel) { Text("Cancel") }
Button(onClick = onVoiceRecordSend) { Text("Send") }
}
}
}
}
} }
} }
@@ -2138,6 +2124,55 @@ private fun formatDateSeparatorLabel(date: LocalDate): String {
} }
} }
@Composable
private fun VoiceRecordingStatusRow(
durationMs: Long,
hint: String?,
isLocked: Boolean,
onCancel: () -> Unit,
onSend: () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 2.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Voice ${formatDuration(durationMs.toInt())}",
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.SemiBold,
)
if (!hint.isNullOrBlank()) {
Text(
text = hint,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
)
}
}
if (isLocked) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = onCancel) { Text("Cancel") }
Button(onClick = onSend) { Text("Send") }
}
}
}
}
}
private fun TextFieldValue.insertAtCursor(value: String): TextFieldValue { private fun TextFieldValue.insertAtCursor(value: String): TextFieldValue {
val start = selection.min val start = selection.min
val end = selection.max val end = selection.max