From 067a6bd8017275edd83b7248a285cea9fb5fd8bf Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 28 Oct 2026 10:06:03 +0100 Subject: [PATCH] Switch camera menus in one tap and toggle the current menu --- .../chat/ui/screens/CameraRecordScreen.kt | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CameraRecordScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CameraRecordScreen.kt index 729b1739..42138e52 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CameraRecordScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CameraRecordScreen.kt @@ -17,6 +17,7 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.* import androidx.camera.view.PreviewView import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -30,6 +31,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -39,6 +41,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.PopupProperties import androidx.core.content.ContextCompat import com.utun.chat.R import com.utun.chat.data.* @@ -326,9 +329,12 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) - val idle = phase == CapturePhase.IDLE var menu by remember(mode, lens?.cameraId) { mutableStateOf(null) } + // Кнопки сами переключают меню; фон и Back закрывают его без перехвата нажатий на соседние кнопки. + val menuProperties = remember { PopupProperties(focusable = false, dismissOnClickOutside = false) } + BackHandler(menu != null) { menu = null } LaunchedEffect(idle) { if (!idle) menu = null } LaunchedEffect(menu) { LogManager.addLog("DEBUG", "Capture", "Toolbar menu=$menu mode=$mode camera=${lens?.cameraId}") } - Scaffold(topBar = { + Scaffold(modifier = Modifier.pointerInput(menu) { if (menu != null) detectTapGestures { menu = null } }, topBar = { TopAppBar(title = { Row(Modifier.horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { CaptureMode.values().forEach { item -> @@ -360,13 +366,14 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) - Box { val name = lens?.let { "${it.label} ${it.mm}".trim() } ?: "—" CaptureControlButton(stringResource(R.string.stream_camera, name), enabled = lenses.isNotEmpty(), - onClick = { menu = CameraOption.CAMERA }) { + onClick = { menu = if (menu == CameraOption.CAMERA) null else CameraOption.CAMERA }) { val focal = lens?.eqFocal?.takeIf { it > 0 }?.let { stringResource(R.string.stream_camera_focal_short, it.roundToInt()) } ?: "—" CaptureValueIcon(CaptureIconShape.CAMERA, focal, front = lens?.type == LensType.SELFIE) } - DropdownMenu(expanded = menu == CameraOption.CAMERA, onDismissRequest = { menu = null }) { + DropdownMenu(expanded = menu == CameraOption.CAMERA, properties = menuProperties, + onDismissRequest = { if (menu == CameraOption.CAMERA) menu = null }) { CaptureMenuHeader(stringResource(R.string.camera)) lenses.forEach { item -> CaptureOptionItem("${item.label} ${item.mm}".trim(), lens == item, onClick = { @@ -379,10 +386,12 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) - Box { if (mode == CaptureMode.VIDEO) { CaptureControlButton(stringResource(R.string.stream_video_codec, codec.label), - enabled = !videoOptions.isNullOrEmpty(), onClick = { menu = CameraOption.CODEC }) { + enabled = !videoOptions.isNullOrEmpty(), + onClick = { menu = if (menu == CameraOption.CODEC) null else CameraOption.CODEC }) { CaptureValueIcon(CaptureIconShape.CHIP, codec.name) } - DropdownMenu(expanded = menu == CameraOption.CODEC, onDismissRequest = { menu = null }) { + DropdownMenu(expanded = menu == CameraOption.CODEC, properties = menuProperties, + onDismissRequest = { if (menu == CameraOption.CODEC) menu = null }) { CaptureMenuHeader(stringResource(R.string.call_video_codec)) videoOptions?.forEach { (item, supported) -> CaptureOptionItem(item.label, codec == item, onClick = { @@ -401,12 +410,13 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) - val label = if (mode == CaptureMode.PHOTO) resolution?.label ?: "—" else quality.label CaptureControlButton(stringResource(R.string.stream_resolution, label), enabled = if (mode == CaptureMode.PHOTO) resolutions.isNotEmpty() else !videoOptions?.get(codec).isNullOrEmpty(), - onClick = { menu = CameraOption.RESOLUTION }) { + onClick = { menu = if (menu == CameraOption.RESOLUTION) null else CameraOption.RESOLUTION }) { val value = if (mode == CaptureMode.PHOTO) resolution?.let { "${minOf(it.width, it.height)}p" } ?: "—" else quality.label CaptureValueIcon(CaptureIconShape.SCREEN, value) } - DropdownMenu(expanded = menu == CameraOption.RESOLUTION, onDismissRequest = { menu = null }) { + DropdownMenu(expanded = menu == CameraOption.RESOLUTION, properties = menuProperties, + onDismissRequest = { if (menu == CameraOption.RESOLUTION) menu = null }) { CaptureMenuHeader(stringResource(R.string.call_video_resolution)) if (mode == CaptureMode.PHOTO) resolutions.forEach { item -> CaptureOptionItem(item.label, resolution == item, onClick = { @@ -425,10 +435,11 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) - if (mode == CaptureMode.VIDEO) { val description = stringResource(R.string.stream_bitrate_value, bitrate / 1_000_000.0) CaptureControlButton(stringResource(R.string.stream_bitrate, description), - onClick = { menu = CameraOption.BITRATE }) { + onClick = { menu = if (menu == CameraOption.BITRATE) null else CameraOption.BITRATE }) { CaptureValueIcon(CaptureIconShape.GAUGE, captureBitrateLabel(bitrate)) } - DropdownMenu(expanded = menu == CameraOption.BITRATE, onDismissRequest = { menu = null }) { + DropdownMenu(expanded = menu == CameraOption.BITRATE, properties = menuProperties, + onDismissRequest = { if (menu == CameraOption.BITRATE) menu = null }) { CaptureMenuHeader(stringResource(R.string.capture_bitrate_title)) VIDEO_BITRATES.forEach { item -> CaptureOptionItem(stringResource(R.string.stream_bitrate_value, item / 1_000_000.0), bitrate == item, @@ -440,10 +451,11 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) - } } else { CaptureControlButton(stringResource(R.string.capture_jpeg_quality, jpegQuality), - onClick = { menu = CameraOption.JPEG_QUALITY }) { + onClick = { menu = if (menu == CameraOption.JPEG_QUALITY) null else CameraOption.JPEG_QUALITY }) { CaptureValueIcon(CaptureIconShape.GAUGE, jpegQuality.toString(), detail = "JPEG") } - DropdownMenu(expanded = menu == CameraOption.JPEG_QUALITY, onDismissRequest = { menu = null }) { + DropdownMenu(expanded = menu == CameraOption.JPEG_QUALITY, properties = menuProperties, + onDismissRequest = { if (menu == CameraOption.JPEG_QUALITY) menu = null }) { CaptureMenuHeader(stringResource(R.string.capture_jpeg_quality_title)) JPEG_QUALITIES.forEach { item -> CaptureOptionItem(item.toString(), jpegQuality == item, onClick = {