Keep Android publisher screen focused on capture controls

This commit is contained in:
evgeny committed 2026-10-20 20:10:36 +02:00
1 parent fe9c3c74bd
commit ca7e169811
2 files changed
+11 -27

No files matched your search

@@ -468,7 +468,7 @@ class MainActivity : AppCompatActivity() {
val selected by vm.streamSelection.collectAsState()
val viewAll by vm.streamViewAll.collectAsState()
val failed by vm.streamSourcesFailed.collectAsState()
StreamScreen(channel.id, channel.name, sources, selected, failed, vm::selectStream, viewAll, vm::watchAllStreams) {
StreamScreen(channel.id, channel.name, sources, selected, failed, vm::selectStream, viewAll) {
vm.selectStream(null); screen = Screen.Chat
}
}
@@ -18,7 +18,6 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.VolumeOff
@@ -75,7 +74,7 @@ import kotlin.math.roundToInt
@Composable
fun StreamScreen(channelId: String, channelName: String, sources: List<StreamSource>, selected: StreamSource?,
sourcesFailed: Boolean, onSelect: (StreamSource?) -> Unit, viewAll: Boolean = false,
onViewAll: () -> Unit = {}, onBack: () -> Unit) {
onBack: () -> Unit) {
val context = LocalContext.current
val group = remember(channelId) { channelId.toULongOrNull()?.toLong() ?: 0L }
if (viewAll) {
@@ -91,7 +90,6 @@ fun StreamScreen(channelId: String, channelName: String, sources: List<StreamSou
val displayDegrees = StreamDisplayDegrees()
val state by StreamController.state.collectAsState()
val local = state?.takeIf { it.group == group }
var previewedSession by remember(group) { mutableStateOf<Long?>(null) }
var audio by remember { mutableStateOf(true) }
var config by remember { mutableStateOf<StreamCaptureConfig?>(null) }
var cameras by remember { mutableStateOf<List<StreamCamera>>(emptyList()) }
@@ -115,22 +113,19 @@ fun StreamScreen(channelId: String, channelName: String, sources: List<StreamSou
val cameraGranted = permissionVersion.let { ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED }
val micGranted = permissionVersion.let { ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED }
LaunchedEffect(sources, local?.id, local?.phase) {
if (local != null && !local.ongoing && selected?.self == true) onSelect(null)
if (local?.phase == StreamPhase.LIVE && previewedSession != local.id) sources.firstOrNull { it.self }?.let {
onSelect(it); previewedSession = local.id
}
}
LaunchedEffect(local?.config, local?.audioAvailable) {
local?.config?.let { config = it; audio = local.audioAvailable }
}
val ongoing = local?.takeIf { it.ongoing }
LaunchedEffect(group, local?.id, local?.phase) {
LogManager.addLog("INFO", "stream", "publish screen group=$group publisher=${local?.id} phase=${local?.phase} " +
"directPreview=${ongoing != null} watchChoices=false")
}
val shownConfig = if (ongoing != null) ongoing.config ?: ongoing.pendingConfig else config
val shownAudio = ongoing?.audioEnabled ?: audio
LaunchedEffect(local?.phase, local?.pendingConfig, selected?.id, shownConfig, shownAudio, displayDegrees) {
LogManager.addLog("DEBUG", "stream", "screen group=$group phase=${local?.phase} preview=${selected?.id} " +
LaunchedEffect(local?.phase, local?.pendingConfig, local?.id, shownConfig, shownAudio, displayDegrees) {
LogManager.addLog("DEBUG", "stream", "screen group=$group phase=${local?.phase} publisher=${local?.id} " +
"config=$shownConfig audio=$shownAudio display=$displayDegrees pending=${local?.pendingConfig}")
}
@@ -146,7 +141,6 @@ fun StreamScreen(channelId: String, channelName: String, sources: List<StreamSou
})
}) { padding ->
Column(Modifier.fillMaxSize().padding(padding).padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedButton(onClick = onViewAll, modifier = Modifier.fillMaxWidth()) { Text(stringResource(R.string.stream_watch_all)) }
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
StreamCaptureToolbar(cameras, shownConfig, displayDegrees, Modifier.weight(1f),
enabled = if (ongoing != null) ongoing.phase == StreamPhase.LIVE && ongoing.pendingConfig == null else !loadingModes,
@@ -188,24 +182,14 @@ fun StreamScreen(channelId: String, channelName: String, sources: List<StreamSou
if (ongoing.phase == StreamPhase.STOPPING) Text(stringResource(R.string.stream_stopping))
StreamTxPanel(ongoing.id)
}
if (selected != null) {
if (ongoing != null) {
Box(Modifier.weight(1f).fillMaxWidth()) {
if (selected.self && local?.ongoing != true) Text(stringResource(R.string.stream_ended))
else key(group, selected.id, selected.epoch) {
local?.preview?.let { LiveStreamView(it, displayDegrees) }
}
key(ongoing.id) { LiveStreamView(ongoing.preview, displayDegrees) }
}
} else {
if (ongoing == null && !loadingModes && cameras.isEmpty())
if (!loadingModes && cameras.isEmpty())
Text(stringResource(R.string.stream_no_modes), color = MaterialTheme.colorScheme.error)
local?.error?.let { Text(stringResource(it), color = MaterialTheme.colorScheme.error) }
if (sourcesFailed) Text(stringResource(R.string.stream_list_failed), color = MaterialTheme.colorScheme.error)
else if (sources.isEmpty() && local?.ongoing != true) Text(stringResource(R.string.stream_none))
Column(Modifier.fillMaxWidth().heightIn(max = 160.dp).verticalScroll(rememberScrollState())) {
sources.forEach { source ->
TextButton(onClick = { onSelect(source) }) { Text(stringResource(R.string.stream_watch, source.displayName)) }
}
}
}
}
}