Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
753ab3a3ab | ||
|
|
f157323626 |
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user