ui: BACK = up the hierarchy (channel->channels->home->exit guard)
The BACK-opens-drawer behavior made navigation loops with the drawer focus restore; replaced with the TV-standard: BACK leaves the player, closes the drawer, leaves a channel drilldown, or goes Home; from Home it is the double-press exit guard. Drawer opens via the account button / LEFT on a leftmost card.
This commit is contained in:
parent
d55c265469
commit
828bfdafa9
2 changed files with 36 additions and 23 deletions
|
|
@ -49,6 +49,7 @@ import androidx.tv.material3.Border
|
|||
import androidx.tv.material3.Card
|
||||
import androidx.tv.material3.CardDefaults
|
||||
import coil.compose.AsyncImage
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import net.jeena.jtubetv.AppGraph
|
||||
|
|
@ -149,6 +150,11 @@ class ChannelsViewModel(private val store: TokenStore) : ViewModel() {
|
|||
val known = all.map { it.name to it.host }.toSet()
|
||||
all += page.first.filter { c -> (c.name to c.host) !in known }
|
||||
start += 100
|
||||
android.util.Log.w(
|
||||
"JTube",
|
||||
"channels page " + (start / 100) + ": total=" + total + " all=" + all.size +
|
||||
" locals=" + all.count { it.host == host },
|
||||
)
|
||||
allChannels = all
|
||||
rebuildRows(host)
|
||||
}
|
||||
|
|
@ -156,6 +162,8 @@ class ChannelsViewModel(private val store: TokenStore) : ViewModel() {
|
|||
|
||||
// stats (videos + plays) for every local channel: throttled parallel-ish
|
||||
val locals = all.filter { it.host == host }
|
||||
Log.w("JTube", "pages done: all=" + all.size + " locals=" + locals.size +
|
||||
" userChannels=" + userChannels.size)
|
||||
channelStatsLoop(locals, host)
|
||||
} catch (e: Exception) {
|
||||
error = e.message ?: "Could not load channels"
|
||||
|
|
@ -171,7 +179,9 @@ class ChannelsViewModel(private val store: TokenStore) : ViewModel() {
|
|||
for (channel in channels) {
|
||||
val key = channel.name + "@" + channel.host
|
||||
if (statsMap.containsKey(key)) continue
|
||||
val cs = repo.channelStats(key).getOrNull() ?: continue
|
||||
val cs = repo.channelStats(key).getOrNull()
|
||||
if (cs == null) { android.util.Log.w("JTube", "stats FAIL " + key); continue }
|
||||
android.util.Log.w("JTube", "stats " + key + " videos=" + cs.videos + " plays=" + cs.plays)
|
||||
statsMap[key] = cs
|
||||
stats = statsMap.toMap()
|
||||
rebuildRows(host)
|
||||
|
|
@ -230,6 +240,14 @@ class ChannelsViewModel(private val store: TokenStore) : ViewModel() {
|
|||
.thenByDescending { g -> g.channels.sumOf { statsMap[it.name + "@" + it.host]?.videos ?: 0 } }
|
||||
.thenByDescending { g -> g.channels.sumOf { it.followersCount } },
|
||||
)
|
||||
Log.w(
|
||||
"JTube",
|
||||
"order now: " + userRows.take(6).joinToString(" | ") { g ->
|
||||
g.ownerName + "(" + g.channels.size + "ch, " +
|
||||
g.channels.sumOf { statsMap[it.name + "@" + it.host]?.videos ?: 0 } + "v" +
|
||||
(if (g.channels.any { it.name in ownNames }) ", OWN" else "") + ")"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun ownerKey(c: VideoChannelDto) = c.ownerAccount?.name ?: ""
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi
|
|||
val epoch by viewModel.sessionEpoch.collectAsState()
|
||||
var destination by rememberSaveable { mutableStateOf(Destination.Home) }
|
||||
var channelDetails by rememberSaveable { mutableStateOf<String?>(null) } // handle
|
||||
var playingVideo by rememberSaveable { mutableStateOf<String?>(null) } // shortUUID
|
||||
// NOT saveable: launching the app should never resume a half-watched player session.
|
||||
var playingVideo by remember { mutableStateOf<String?>(null) } // shortUUID
|
||||
var restoreToken by rememberSaveable { mutableStateOf(0) } // increments when playback ends
|
||||
var drawerOpen by rememberSaveable { mutableStateOf(false) }
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
|
|
@ -89,35 +90,29 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi
|
|||
if (playingVideo == null && restoreToken >= 1) restoreToken++
|
||||
}
|
||||
|
||||
// BACK order: player (own BackHandler) → drawer close → channel drilldown →
|
||||
// BACK (on content) opens the drawer; a double-BACK with the drawer open exits.
|
||||
BackHandler(enabled = playingVideo == null && channelDetails == null) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (drawerOpen) {
|
||||
// BACK order (TV standard): player (own handler) → drawer close → channel
|
||||
// drilldown → non-Home destination → Home; from Home: double-press exits.
|
||||
BackHandler(enabled = drawerOpen && playingVideo == null) {
|
||||
drawerOpen = false
|
||||
runCatching { searchFocus.requestFocus() }
|
||||
}
|
||||
BackHandler(enabled = !drawerOpen && playingVideo == null && channelDetails != null) {
|
||||
channelDetails = null
|
||||
}
|
||||
BackHandler(enabled = !drawerOpen && playingVideo == null && channelDetails == null) {
|
||||
if (destination != Destination.Home) {
|
||||
channelDetails = null
|
||||
destination = Destination.Home
|
||||
} else {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastBackAt < 2500) {
|
||||
(context as? android.app.Activity)?.finish()
|
||||
return@BackHandler
|
||||
}
|
||||
lastBackAt = now
|
||||
android.widget.Toast.makeText(context, "Press BACK again to exit", android.widget.Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
lastBackAt = now
|
||||
drawerOpen = true
|
||||
}
|
||||
}
|
||||
BackHandler(enabled = playingVideo == null && channelDetails != null && !drawerOpen) {
|
||||
channelDetails = null
|
||||
}
|
||||
BackHandler(enabled = drawerOpen && playingVideo == null) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastBackAt < 2500) {
|
||||
(context as? android.app.Activity)?.finish()
|
||||
return@BackHandler
|
||||
}
|
||||
lastBackAt = now
|
||||
drawerOpen = false
|
||||
runCatching { searchFocus.requestFocus() }
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue