Expose watch-all streams from Android chat and preserve catalogue on transient errors

This commit is contained in:
evgeny committed 2026-10-20 17:07:31 +02:00
1 parent 9e5b2ad435
commit 5f0325e817
3 files changed
+24 -15

No files matched your search

@@ -332,7 +332,7 @@ class MainActivity : AppCompatActivity() {
Screen.MemberList -> screen = Screen.Chat
Screen.VideoRecord -> screen = if (videoTarget is ConversationTarget.Pm) Screen.Dm else Screen.Chat
Screen.PhotoRecord -> screen = Screen.Chat
Screen.Streams -> screen = Screen.Chat
Screen.Streams -> { vm.selectStream(null); screen = Screen.Chat }
Screen.Chat -> screen = Screen.ChannelList
Screen.Dm -> { vm.closeDm(); screen = Screen.ChannelList }
Screen.Call -> screen = screenBeforeCall ?: Screen.ChannelList
@@ -421,6 +421,7 @@ class MainActivity : AppCompatActivity() {
onBack = { screen = Screen.ChannelList },
onStreams = { vm.selectStream(null); screen = Screen.Streams },
onWatchStream = { vm.selectStream(it); screen = Screen.Streams },
onWatchAllStreams = { vm.watchAllStreams(); screen = Screen.Streams },
onPlayVideo = { msg -> videoMessage = msg },
onViewPhoto = { msg -> photoMessage = msg },
onRecordVideo = {
@@ -441,8 +442,9 @@ class MainActivity : AppCompatActivity() {
vm.currentChannel.collectAsState().value?.let { channel ->
val sources by vm.streamSources.collectAsState()
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) {
StreamScreen(channel.id, channel.name, sources, selected, failed, vm::selectStream, viewAll, vm::watchAllStreams) {
vm.selectStream(null); screen = Screen.Chat
}
}
@@ -92,7 +92,8 @@ fun ChatScreen(
onRecordVideo: () -> Unit = {},
onCapturePhoto: () -> Unit = {},
onStreams: () -> Unit = {},
onWatchStream: (StreamSource) -> Unit = {}
onWatchStream: (StreamSource) -> Unit = {},
onWatchAllStreams: () -> Unit = {}
) {
val listState = rememberLazyListState()
val context = LocalContext.current
@@ -103,11 +104,14 @@ fun ChatScreen(
var menu by remember(channel.id) { mutableStateOf(false) }
val streams by viewModel.streamSources.collectAsState()
var chooseStream by remember(channel.id) { mutableStateOf(false) }
if (chooseStream && streams.size > 1) AlertDialog(
if (chooseStream) AlertDialog(
onDismissRequest = { chooseStream = false },
title = { Text(stringResource(R.string.stream_choose_source)) },
text = {
Column(Modifier.heightIn(max = 360.dp).verticalScroll(rememberScrollState())) {
TextButton(onClick = { chooseStream = false; onWatchAllStreams() }) {
Text(stringResource(R.string.stream_watch_all))
}
streams.forEach { source ->
TextButton(onClick = { chooseStream = false; onWatchStream(source) }) {
Text("${source.displayName} · ${source.id.toULong()}")
@@ -117,7 +121,6 @@ fun ChatScreen(
},
confirmButton = { TextButton(onClick = { chooseStream = false }) { Text(stringResource(R.string.cancel)) } }
)
LaunchedEffect(streams) { if (streams.size <= 1) chooseStream = false }
/* рация */
val radioActive by viewModel.radioActive.collectAsState()
@@ -192,9 +195,7 @@ fun ChatScreen(
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.back)) }
},
actions = {
if (streams.isNotEmpty()) IconButton(onClick = {
if (streams.size == 1) onWatchStream(streams.single()) else chooseStream = true
}) {
IconButton(onClick = { chooseStream = true }) {
BadgedBox(badge = { if (streams.size > 1) Badge { Text(streams.size.toString()) } }) {
Icon(Icons.Filled.LiveTv, stringResource(R.string.stream_available, streams.size), tint = Color(0xFFE53935))
}
@@ -228,11 +228,17 @@ class ChatViewModel : ViewModel() {
val streamSourcesFailed: StateFlow<Boolean> = _streamSourcesFailed
private val _streamSelection = MutableStateFlow<StreamSource?>(null)
val streamSelection: StateFlow<StreamSource?> = _streamSelection
private val _streamViewAll = MutableStateFlow(false)
val streamViewAll: StateFlow<Boolean> = _streamViewAll
fun selectStream(source: StreamSource?) {
_streamSelection.value = source
_streamSelection.value = source; _streamViewAll.value = false
LogManager.addLog("INFO", "stream", "selected channel=${_currentChannel.value?.id} source=${source?.id} epoch=${source?.epoch}")
}
fun watchAllStreams() {
_streamSelection.value = null; _streamViewAll.value = true
LogManager.addLog("INFO", "stream", "watch all active streams channel=${_currentChannel.value?.id}")
}
private fun watchStreams() {
if (!NativeLib.isReady()) return
@@ -379,7 +385,7 @@ class ChatViewModel : ViewModel() {
}
viewModelScope.launch {
CallController.state.collect { call ->
if (call?.preparing == true || CallController.blocksMedia()) _streamSelection.value = null
if (call?.preparing == true || CallController.blocksMedia()) selectStream(null)
}
}
viewModelScope.launch {
@@ -596,7 +602,7 @@ class ChatViewModel : ViewModel() {
4 -> { if (repo != null) { LogManager.addLog("INFO", "VM", "CHANNEL_UPDATED — refreshChannels"); refreshChannels() } else { LogManager.addLog("WARN", "VM", "CHANNEL_UPDATED skipped — repo is NULL") } }
12 -> { LogManager.addLog("INFO", "VM", "service started"); watchStreams() }
13 -> {
_streamSources.value = emptyList(); _streamSourcesFailed.value = false; _streamSelection.value = null
_streamSources.value = emptyList(); _streamSourcesFailed.value = false; selectStream(null)
for (id in submissions.keys.toList()) submissionResult(id, false)
_radioTalkState.value = RadioTalkState()
_radioTalking.value = null
@@ -715,10 +721,10 @@ class ChatViewModel : ViewModel() {
if (_currentChannel.value?.id?.toULongOrNull()?.toLong() != group || !_foreground.value) return
try {
_streamSourcesFailed.value = data[8].toInt() == 0
_streamSources.value = StreamSource.parse(String(data, 9, data.size - 9, Charsets.UTF_8))
if (!_streamSourcesFailed.value) _streamSources.value = StreamSource.parse(String(data, 9, data.size - 9, Charsets.UTF_8))
LogManager.addLog("INFO", "stream", "sources group=$group count=${_streamSources.value.size} valid=${!_streamSourcesFailed.value}")
} catch (e: Exception) {
_streamSources.value = emptyList(); _streamSourcesFailed.value = true
_streamSourcesFailed.value = true
LogManager.addLog("ERROR", "stream", "source snapshot parse group=$group: ${e.message}")
}
}
@@ -1341,7 +1347,7 @@ class ChatViewModel : ViewModel() {
fun selectChannel(channel: Channel) {
val r = repo ?: return
if (_currentChannel.value?.id != channel.id) {
_streamSources.value = emptyList(); _streamSourcesFailed.value = false; _streamSelection.value = null
_streamSources.value = emptyList(); _streamSourcesFailed.value = false; selectStream(null)
_historyAnchor.value = ""
_messageJump.value = null
pendingLatestJump = null
@@ -1923,7 +1929,7 @@ class ChatViewModel : ViewModel() {
_channels.value = emptyList()
_messages.value = emptyList()
_currentChannel.value = null
_streamSources.value = emptyList(); _streamSelection.value = null; _streamSourcesFailed.value = false
_streamSources.value = emptyList(); selectStream(null); _streamSourcesFailed.value = false
_generatingKeys.value = false
_localSockets.value = emptyList()
_channelOwner.value = false