feat(android): add Android release workflow and shuffle UI improvements
Android Release / Build signed release APK (push) Canceled after 40m19s

Adds a new Android release workflow to sign and publish APKs when tags are pushed. Also enhances wallpaper shuffle by tracking seen IDs to reduce repeats. UI changes in App.tsx introduce lazy loading for the gallery and improved navigation/history handling.

- Add Android signing workflow with keystore secrets
- Track shuffle seen IDs to avoid image repeats
- Enable gallery lazy loading via sentinel and observer
This commit is contained in:
2026-08-25 23:22:36 +02:00
parent a5f71b576b
commit 7c15f51355
5 changed files with 244 additions and 20 deletions
@@ -22,6 +22,7 @@ object WallpaperStore {
private const val KEY_INDEX = "current_index"
private const val KEY_CURRENT_ID = "current_entry_id"
private const val KEY_INTERVAL = "interval_minutes"
private const val KEY_SHUFFLE_SEEN_IDS = "shuffle_seen_ids"
private const val CROP_PREFIX = "crop_"
private const val INVALID_PREFIX = "invalid_"
private const val RENDER_CACHE_MAX_BYTES = 128L * 1024 * 1024
@@ -107,7 +108,9 @@ object WallpaperStore {
fun set(context: Context, name: String, value: Boolean) {
require(name in setOf("shuffle", "lockScreenOnly", "allowMobileData", "prefetchImmich")) { "Unbekannte Einstellung" }
prefs(context).edit().putBoolean(name, value).apply()
val editor = prefs(context).edit().putBoolean(name, value)
if (name == "shuffle") editor.remove(KEY_SHUFFLE_SEEN_IDS)
editor.apply()
if (name == "prefetchImmich") {
if (value && ImmichClient.configured(context)) ImmichPrefetchWorker.enqueue(context)
else if (!value) ImmichPrefetchWorker.cancel(context)
@@ -390,9 +393,21 @@ object WallpaperStore {
val items = entries(context)
if (items.isEmpty()) return false
val previous = currentIndex(context, items)
val candidates = if (shuffle(context) && items.size > 1) {
items.indices.filter { it != previous }.shuffled() + previous
} else (1..items.size).map { (previous + it).mod(items.size) }
if (shuffle(context) && items.size > 1) {
val availableIds = items.mapTo(mutableSetOf()) { it.id }
val seenIds = prefs(context).getStringSet(KEY_SHUFFLE_SEEN_IDS, emptySet())
.orEmpty().filterTo(mutableSetOf()) { it in availableIds }
seenIds.add(items[previous].id)
var candidates = items.indices.filter { items[it].id !in seenIds }.shuffled()
if (candidates.isEmpty()) {
seenIds.clear()
seenIds.add(items[previous].id)
candidates = items.indices.filter { it != previous }.shuffled()
}
return applyCandidates(context, items, candidates, automatic, seenIds)
}
val candidates = (1..items.size).map { (previous + it).mod(items.size) }
return applyCandidates(context, items, candidates, automatic)
}
@@ -401,10 +416,21 @@ object WallpaperStore {
val items = entries(context)
val index = items.indexOfFirst { it.id == id }
if (index < 0) return false
return applyCandidates(context, items, listOf(index), automatic = false)
val seenIds = if (shuffle(context)) {
val availableIds = items.mapTo(mutableSetOf()) { it.id }
prefs(context).getStringSet(KEY_SHUFFLE_SEEN_IDS, emptySet())
.orEmpty().filterTo(mutableSetOf()) { it in availableIds }
} else null
return applyCandidates(context, items, listOf(index), automatic = false, seenIds)
}
private fun applyCandidates(context: Context, items: List<Entry>, candidates: List<Int>, automatic: Boolean): Boolean {
private fun applyCandidates(
context: Context,
items: List<Entry>,
candidates: List<Int>,
automatic: Boolean,
shuffleSeenIds: Set<String>? = null,
): Boolean {
val unavailableServers = mutableSetOf<String>()
for (index in candidates) {
val entry = items[index]
@@ -447,7 +473,12 @@ object WallpaperStore {
val manager = WallpaperManager.getInstance(context)
if (android.os.Build.VERSION.SDK_INT >= 24 && lockOnly(context)) manager.setBitmap(wallpaper, null, true, WallpaperManager.FLAG_LOCK)
else manager.setBitmap(wallpaper)
prefs(context).edit().remove(INVALID_PREFIX + entry.id).putString(KEY_CURRENT_ID, entry.id).putInt(KEY_INDEX, index).apply()
val editor = prefs(context).edit()
.remove(INVALID_PREFIX + entry.id)
.putString(KEY_CURRENT_ID, entry.id)
.putInt(KEY_INDEX, index)
if (shuffleSeenIds != null) editor.putStringSet(KEY_SHUFFLE_SEEN_IDS, shuffleSeenIds + entry.id)
editor.apply()
return true
} catch (_: Exception) {
// Try the next usable entry without changing the current selection.