Release v0.2.39
Publish Memby release / release (push) Canceled after 0s

This commit is contained in:
ponzischeme89
2026-08-09 16:57:14 +12:00
parent 52e167ea59
commit 598a4f5c75
7 changed files with 206 additions and 3 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ val projectNoticeText =
// A release workflow can derive the app version from its Git tag without editing the
// source tree. Local builds keep using the checked-in default.
val defaultVersionName = "0.2.38"
val defaultVersionName = "0.2.39"
val membyVersionName: String =
(project.findProperty("memby.versionName") as String?)
?.trim()
@@ -304,6 +304,9 @@ data class Settings(
val updateBaseUrl: String? = null,
val updateRepo: String? = null,
val updateToken: String? = null,
// Device-level: Back-button behaviour belongs to the television and its remote, not
// to the signed-in viewer, so it deliberately does not travel through UserPreferences.
val confirmExitMemby: Boolean = false,
// Show each item's Emby "Logo" image in place of the plain-text title.
val showTitleLogo: Boolean = true,
// Slide up a "next up" banner near the end of an episode and roll into the next one.
@@ -487,6 +490,7 @@ class SettingsStore(private val context: Context) {
val UPDATE_BASE_URL = stringPreferencesKey("update_base_url")
val UPDATE_REPO = stringPreferencesKey("update_repo")
val UPDATE_TOKEN = stringPreferencesKey("update_token")
val CONFIRM_EXIT_MEMBY = booleanPreferencesKey("confirm_exit_memby")
val SHOW_TITLE_LOGO = booleanPreferencesKey("show_title_logo")
val AUTO_PLAY_NEXT = booleanPreferencesKey("auto_play_next_episode")
val SHOW_TEN_MINUTE_REMINDER = booleanPreferencesKey("show_ten_minute_reminder")
@@ -570,6 +574,10 @@ class SettingsStore(private val context: Context) {
}
}
suspend fun setConfirmExitMemby(enabled: Boolean) {
context.dataStore.edit { it[Keys.CONFIRM_EXIT_MEMBY] = enabled }
}
/**
* Ids of alerts already shown on this TV. The gateway keeps offering an alert for as
* long as it is current, so without this an "aired" banner would return every poll —
@@ -1344,6 +1352,7 @@ class SettingsStore(private val context: Context) {
updateBaseUrl = preferences[Keys.UPDATE_BASE_URL],
updateRepo = preferences[Keys.UPDATE_REPO],
updateToken = preferences[Keys.UPDATE_TOKEN],
confirmExitMemby = preferences[Keys.CONFIRM_EXIT_MEMBY] ?: false,
showTitleLogo = preferences[Keys.SHOW_TITLE_LOGO] ?: true,
autoPlayNextEpisode = preferences[Keys.AUTO_PLAY_NEXT] ?: true,
showTenMinuteReminder = preferences[Keys.SHOW_TEN_MINUTE_REMINDER] ?: true,
@@ -232,6 +232,7 @@ private fun AppRoot(onCloseSettings: () -> Unit) {
var appUpdate by remember { mutableStateOf<GatewayUpdate?>(null) }
var initialUpdateCheckComplete by remember { mutableStateOf(false) }
var dismissedUpdateVersion by rememberSaveable { mutableStateOf<String?>(null) }
var confirmingExit by rememberSaveable { mutableStateOf(false) }
// SettingsStore starts eagerly in Application.onCreate. Reuse its in-memory value when
// Android recreates this activity after the viewer returns from the TV home screen;
// starting from null needlessly painted "Opening Memby..." while the replayed value
@@ -415,7 +416,9 @@ private fun AppRoot(onCloseSettings: () -> Unit) {
!InstallPermission.granted(LocalContext.current) ->
InstallPermissionScreen(onContinue = { installPermissionHandled = true })
loaded.isSignedIn -> {
BackHandler(onBack = onCloseSettings)
BackHandler {
if (loaded.confirmExitMemby) confirmingExit = true else onCloseSettings()
}
key(loaded.userId, loaded.serverUrl) {
HomeScreen(settings = loaded)
}
@@ -446,6 +449,69 @@ private fun AppRoot(onCloseSettings: () -> Unit) {
)
else -> FirstRunScreen(onGetStarted = { startingFirstRun = true })
}
if (confirmingExit) {
ExitMembyConfirmation(
onStay = { confirmingExit = false },
onExit = onCloseSettings,
)
}
}
}
@Composable
private fun ExitMembyConfirmation(
onStay: () -> Unit,
onExit: () -> Unit,
) {
val stayFocus = remember { FocusRequester() }
val exitFocus = remember { FocusRequester() }
BackHandler(onBack = onStay)
LaunchedEffect(Unit) {
kotlinx.coroutines.delay(16L)
runCatching { stayFocus.requestFocus() }
}
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(20f)
.background(Color.Black.copy(alpha = 0.82f)),
contentAlignment = Alignment.Center,
) {
Column(
modifier = Modifier
.width(480.dp)
.background(Color(0xFF20262B), RoundedCornerShape(18.dp))
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(18.dp),
) {
Text(
"Close Memby?",
color = Color.White,
fontSize = 26.sp,
fontWeight = FontWeight.SemiBold,
)
Text(
"Choose Stay to keep browsing, or close Memby and return to your TV.",
color = Color(0xFFBCC4CA),
fontSize = 16.sp,
textAlign = TextAlign.Center,
)
Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) {
Button(
onClick = onStay,
modifier = Modifier
.focusRequester(stayFocus)
.focusProperties { right = exitFocus },
) { Text("Stay in Memby") }
Button(
onClick = onExit,
modifier = Modifier
.focusRequester(exitFocus)
.focusProperties { left = stayFocus },
) { Text("Close Memby") }
}
}
}
}
@@ -208,6 +208,7 @@ internal data class SettingsPanelState(
val showCardMetadata: Boolean = true,
val showRatingsStrip: Boolean = true,
val hideWatchedMovies: Boolean = false,
val confirmExitMemby: Boolean = false,
val welcomeQuoteStyle: String = WelcomeQuoteStyle.NEUTRAL.value,
/**
* The viewer's own colour scheme, and the ones the gateway says they may pick between.
@@ -264,6 +265,7 @@ internal data class SettingsPanelActions(
val onShowCardMetadataChanged: (Boolean) -> Unit = {},
val onShowRatingsStripChanged: (Boolean) -> Unit = {},
val onHideWatchedMoviesChanged: (Boolean) -> Unit = {},
val onConfirmExitMembyChanged: (Boolean) -> Unit = {},
val onWelcomeQuoteStyleChanged: (String) -> Unit = {},
val onThemeChanged: (String) -> Unit = {},
val onPageSelected: (SettingsPage) -> Unit = {},
@@ -312,6 +314,7 @@ fun SettingsSheet(
var showCardMetadata by rememberSaveable { mutableStateOf(settings.showHomeCardMetadata) }
var showRatingsStrip by rememberSaveable { mutableStateOf(settings.showRatingsStrip) }
var hideWatchedMovies by rememberSaveable { mutableStateOf(settings.hideWatchedMovies) }
var confirmExitMemby by rememberSaveable { mutableStateOf(settings.confirmExitMemby) }
var welcomeQuoteStyle by rememberSaveable { mutableStateOf(settings.welcomeQuoteStyle) }
// The resolved theme and the schemes on offer both come from the gateway, through the
// sync that owns them. Collected here rather than in SettingsPanelContent so the content
@@ -375,6 +378,7 @@ fun SettingsSheet(
settings.showHomeCardMetadata,
settings.showRatingsStrip,
settings.hideWatchedMovies,
settings.confirmExitMemby,
settings.autoPlayNextEpisode,
settings.showTenMinuteReminder,
settings.seekIntervalSeconds,
@@ -395,6 +399,7 @@ fun SettingsSheet(
showCardMetadata = settings.showHomeCardMetadata
showRatingsStrip = settings.showRatingsStrip
hideWatchedMovies = settings.hideWatchedMovies
confirmExitMemby = settings.confirmExitMemby
welcomeQuoteStyle = settings.welcomeQuoteStyle
}
@@ -421,6 +426,7 @@ fun SettingsSheet(
showCardMetadata = showCardMetadata,
showRatingsStrip = showRatingsStrip,
hideWatchedMovies = hideWatchedMovies,
confirmExitMemby = confirmExitMemby,
welcomeQuoteStyle = welcomeQuoteStyle,
themeId = settings.themeId,
themeOptions = availableThemes.map { theme ->
@@ -510,6 +516,10 @@ fun SettingsSheet(
hideWatchedMovies = it
scope.launch { store.setHideWatchedMovies(it) }
},
onConfirmExitMembyChanged = {
confirmExitMemby = it
scope.launch { store.setConfirmExitMemby(it) }
},
onThemeChanged = { chosen ->
// No local echo: what is on screen is the palette the gateway resolves, and it
// arrives through ThemeSync a moment later. Painting optimistically here would
@@ -850,6 +860,13 @@ internal fun SettingsPanelContent(
checked = state.showRatingsStrip,
onCheckedChange = actions.onShowRatingsStripChanged,
)
SettingDivider()
SettingsToggleRow(
title = "Confirm before closing Memby",
description = "Ask before the Back button closes the app.",
checked = state.confirmExitMemby,
onCheckedChange = actions.onConfirmExitMembyChanged,
)
if (state.hiddenHomeRowCount > 0) {
SettingDivider()
SettingsActionRow(
@@ -185,9 +185,17 @@ class UserPreferencesTest {
token = "session-token",
ringColorHex = "FF0000",
rotationIntervalSeconds = 45,
confirmExitMemby = true,
).toUserPreferences().encode().toString()
listOf("Living room", "device-1", "gitea-secret", "git.example", "session-token")
listOf(
"Living room",
"device-1",
"gitea-secret",
"git.example",
"session-token",
"confirmExitMemby",
)
.forEach { assertTrue("$it reached the wire", it !in encoded) }
}
}