2 Commits

Author SHA1 Message Date
be-nj
753ab3a3ab Follow the repo move to the be-nj organisation
Some checks failed
Build TV app / build (push) Failing after 2s
Updater and README point at the new path. The old one redirects, but the
updater should not depend on that. Also drops a stale GitHub-specific
Accept header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 02:33:47 +02:00
be-nj
f157323626 fix(live): returning from playback keeps the group you were in
Some checks failed
Build TV app / build (push) Failing after 2s
The rail opens whatever gets focused, so when the player closed and focus
landed on "Alle Sender", the view silently switched away from the group
the viewer had been browsing. A restore flag now suppresses that
auto-select until focus has been placed on the channel that was watched.

Completes #13.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 02:20:17 +02:00
5 changed files with 22 additions and 9 deletions

View File

@@ -38,7 +38,7 @@ TV-Fernbedienung oder steuert alles bequem vom Handy aus.
1. Auf dem Google TV die App **Downloader** installieren (oder einen anderen
Weg nutzen, eine APK-Datei zu öffnen).
2. Diese Adresse eingeben:
`git.beckm4nn.net/benjamin/castarr/raw/branch/apk/castarr.apk`
`git.beckm4nn.net/be-nj/castarr/raw/branch/apk/castarr.apk`
3. Installation bestätigen („Unbekannte Quellen" für Downloader erlauben,
wenn der Fernseher fragt).
4. Castarr starten und den drei Schritten auf dem Bildschirm folgen:

View File

@@ -12,8 +12,8 @@ android {
applicationId = "dev.castarr.tv"
minSdk = 26
targetSdk = 35
versionCode = 23
versionName = "0.9.0"
versionCode = 25
versionName = "0.9.2"
}
// Release signing from environment (see ~/.keys/castarr-release.env on the

View File

@@ -51,6 +51,13 @@ class AppState(
/** Channel to restore focus to when the list comes back (#13). */
var lastWatched by mutableStateOf<Channel?>(null)
/**
* True while the list is being restored after playback. The group rail
* opens whatever gets focused, so without this the focus landing there
* would silently switch the view back to "Alle Sender".
*/
var restorePending by mutableStateOf(false)
private val reentryHandler = android.os.Handler(android.os.Looper.getMainLooper())
private var lastStoppedUrl: String = ""
private var lastStoppedAt: Long = 0L
@@ -190,6 +197,7 @@ class AppState(
}
fun stopPlayback() {
restorePending = lastWatched != null
reentryHandler.removeCallbacksAndMessages(null)
lastStoppedUrl = currentChannel?.url.orEmpty()
lastStoppedAt = System.currentTimeMillis()

View File

@@ -106,11 +106,12 @@ fun LiveScreen(state: AppState) {
// A fresh group starts at the top; a return from playback does not.
if (restoreIndex == null) listState.scrollToItem(0)
}
LaunchedEffect(restoreIndex, channels.size) {
if (restoreIndex != null && !restored) {
LaunchedEffect(restoreIndex, channels.size, state.restorePending) {
if (restoreIndex != null && (state.restorePending || !restored)) {
listState.scrollToItem(restoreIndex)
runCatching { restoreFocus.requestFocus() }
restored = true
state.restorePending = false
}
}
val intoList = if (channels.isEmpty()) Modifier
@@ -135,6 +136,7 @@ fun LiveScreen(state: AppState) {
modifier = intoList.then(
if (selected) Modifier.focusRequester(railFocus) else Modifier
),
suppressAutoSelect = { state.restorePending },
) {
state.favoritesOnly = false
state.groupFilter = null
@@ -149,6 +151,7 @@ fun LiveScreen(state: AppState) {
modifier = intoList.then(
if (state.favoritesOnly) Modifier.focusRequester(railFocus) else Modifier
),
suppressAutoSelect = { state.restorePending },
) {
state.favoritesOnly = true
state.groupFilter = null
@@ -172,6 +175,7 @@ fun LiveScreen(state: AppState) {
modifier = intoList.then(
if (state.groupFilter == group) Modifier.focusRequester(railFocus) else Modifier
),
suppressAutoSelect = { state.restorePending },
) {
state.favoritesOnly = false
state.groupFilter = group
@@ -237,6 +241,7 @@ private fun GroupItem(
count: Int,
selected: Boolean,
modifier: Modifier = Modifier,
suppressAutoSelect: () -> Boolean = { false },
onSelect: () -> Unit,
) {
Surface(
@@ -245,7 +250,7 @@ private fun GroupItem(
onClick = onSelect,
modifier = modifier
.fillMaxWidth()
.onFocusChanged { if (it.isFocused) onSelect() },
.onFocusChanged { if (it.isFocused && !suppressAutoSelect()) onSelect() },
shape = ClickableSurfaceDefaults.shape(RoundedCornerShape(10.dp)),
scale = ClickableSurfaceDefaults.scale(focusedScale = 1f),
colors = ClickableSurfaceDefaults.colors(

View File

@@ -18,12 +18,12 @@ object UpdateChecker {
private const val TAG = "UpdateChecker"
private const val LATEST =
"https://git.beckm4nn.net/api/v1/repos/benjamin/castarr/releases/latest"
"https://git.beckm4nn.net/api/v1/repos/be-nj/castarr/releases/latest"
// Release assets need an API token to upload, so the APK is served from
// an orphan branch instead — anonymously fetchable on a public repo.
private const val APK_FALLBACK =
"https://git.beckm4nn.net/benjamin/castarr/raw/branch/apk/castarr.apk"
"https://git.beckm4nn.net/be-nj/castarr/raw/branch/apk/castarr.apk"
private var apkUrl: String = ""
@@ -103,7 +103,7 @@ object UpdateChecker {
return try {
connection.connectTimeout = 10_000
connection.readTimeout = 15_000
connection.setRequestProperty("Accept", "application/vnd.github+json")
connection.setRequestProperty("Accept", "application/json")
connection.inputStream.bufferedReader().use { it.readText() }
} finally {
connection.disconnect()