Switch camera menus in one tap and toggle the current menu

This commit is contained in:
evgeny committed 2026-10-28 12:08:07 +01:00
1 parent 217a4bb27b
commit 067a6bd801
1 file changed
+23 -11
@@ -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<CameraOption?>(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 = {