refactor(android): Web-Adresse aus BuildConfig statt als vierte Einstellung

Der Host stand ohnehin schon beim Bauen fest, weil die App-Links ihn im Manifest
brauchen. Ihn zusaetzlich als Einstellung zu fuehren hiess: eine vierte Adresse
pflegen, die vom Manifest abweichen kann, ohne dass es jemand merkt — und der
Teilen-Eintrag versteckte sich, solange sie leer war, was beim Suchen schlicht
wie ein fehlendes Feature aussah.

Jetzt kommt beides aus -PnovasonicHost=…: das Manifest fuer eingehende Links,
BuildConfig.SHARE_HOST fuer ausgehende. Die Einstellung samt Speicherfeld ist
entfernt, Teilen ist immer sichtbar. versionCode 5 / 1.2, APK hochgeladen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-06 20:27:27 +02:00
1 parent dea0485a54
commit 83570b22b5
6 files changed
+16 -25

No files matched your search

+5 -2
View File
@@ -150,6 +150,10 @@ Versioning: two-part `MAJOR.MINOR` — each release bumps the minor (features an
mutiert, um unter Windows das Neuaufbauen der Medienkachel zu vermeiden. Über MPRIS kam davon mutiert, um unter Windows das Neuaufbauen der Medienkachel zu vermeiden. Über MPRIS kam davon
nichts an: Plasma-Widgets zeigten dauerhaft den zuerst gespielten Titel. Bei echtem Titelwechsel nichts an: Plasma-Widgets zeigten dauerhaft den zuerst gespielten Titel. Bei echtem Titelwechsel
wird jetzt ein neues `MediaMetadata` zugewiesen, sonst weiter in-place (Cover-Nachladen). wird jetzt ein neues `MediaMetadata` zugewiesen, sonst weiter in-place (Cover-Nachladen).
- **Android 1.2 (versionCode 5)** — die Web-Adresse ist keine Einstellung mehr, sondern kommt aus
`BuildConfig.SHARE_HOST`, also aus **derselben** Angabe (`-PnovasonicHost=…`), die ohnehin für die
App-Links ins Manifest wandert. Damit gibt es keine vierte Adresse zu pflegen, „Teilen" ist immer
sichtbar, und ein- und ausgehende Links können nicht mehr auseinanderlaufen.
- **Android 1.1 (versionCode 4)** — erste Version mit Teilen-Link, Songlöschen, Playlist aus dem - **Android 1.1 (versionCode 4)** — erste Version mit Teilen-Link, Songlöschen, Playlist aus dem
Import und Duell-Anzeigetafel. Wichtig für den In-App-Updater: er vergleicht den `versionCode` aus Import und Duell-Anzeigetafel. Wichtig für den In-App-Updater: er vergleicht den `versionCode` aus
der Gitea-Release-Beschreibung mit dem der installierten App. Wird die Version nicht angehoben der Gitea-Release-Beschreibung mit dem der installierten App. Wird die Version nicht angehoben
@@ -163,8 +167,7 @@ Versioning: two-part `MAJOR.MINOR` — each release bumps the minor (features an
nicht erneut losspielt. Als installierte PWA landet der Link dank `launch_handler` im bereits nicht erneut losspielt. Als installierte PWA landet der Link dank `launch_handler` im bereits
offenen Fenster. Android teilt über den Systemdialog (`ACTION_SEND`) und empfängt Links per offenen Fenster. Android teilt über den Systemdialog (`ACTION_SEND`) und empfängt Links per
App-Link-Intent-Filter; der Host kommt beim Bauen aus `-PnovasonicHost=…`, die Web-Adresse für App-Link-Intent-Filter; der Host kommt beim Bauen aus `-PnovasonicHost=…`, die Web-Adresse für
ausgehende Links aus den Einstellungen — **ohne diese Einstellung erscheint „Teilen" im ausgehende Links aus derselben Angabe (seit 1.2, vorher eine eigene Einstellung). Für das Öffnen ohne Nachfrage muss der SHA-256 des
Kontextmenü nicht**, weil es ohne Adresse keinen Link zu bauen gäbe. Für das Öffnen ohne Nachfrage muss der SHA-256 des
Release-Schlüssels in `public/.well-known/assetlinks.json` stehen — Anleitung in Release-Schlüssels in `public/.well-known/assetlinks.json` stehen — Anleitung in
`docs/share-links.md`. Ohne diesen Schritt öffnet der Link die Web-App im Browser. `docs/share-links.md`. Ohne diesen Schritt öffnet der Link die Web-App im Browser.
- **Android: Songs von der Festplatte löschen** — im Song-Kontextmenü, unten abgesetzt und rot, mit - **Android: Songs von der Festplatte löschen** — im Song-Kontextmenü, unten abgesetzt und rot, mit
+6 -2
View File
@@ -14,8 +14,8 @@ android {
applicationId = "com.novasonic.app" applicationId = "com.novasonic.app"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 4 versionCode = 5
versionName = "1.1" versionName = "1.2"
// Host der eigenen NovaSonic-Web-App — Ziel der geteilten Links. Einmal in // Host der eigenen NovaSonic-Web-App — Ziel der geteilten Links. Einmal in
// gradle.properties setzen (novasonicHost=music.example.com); ohne Angabe bleibt der // gradle.properties setzen (novasonicHost=music.example.com); ohne Angabe bleibt der
// Platzhalter stehen und der App-Link greift schlicht nicht. // Platzhalter stehen und der App-Link greift schlicht nicht.
@@ -28,6 +28,10 @@ android {
val host2 = (project.findProperty("novasonicHost2") as String?) ?: host1 val host2 = (project.findProperty("novasonicHost2") as String?) ?: host1
manifestPlaceholders["shareHost"] = host1 manifestPlaceholders["shareHost"] = host1
manifestPlaceholders["shareHost2"] = host2 manifestPlaceholders["shareHost2"] = host2
// Derselbe Host auch fuer AUSGEHENDE Links. Er steht ohnehin schon fest im Manifest, eine
// zusaetzliche Einstellung in der App waere also nur eine weitere Adresse zum Pflegen —
// und eine, die vom Manifest abweichen koennte, ohne dass es jemand merkt.
buildConfigField("String", "SHARE_HOST", "\"$host1\"")
} }
buildFeatures { buildFeatures {
@@ -19,8 +19,6 @@ class PrefsStore(private val context: Context) {
val KEY_LASTFM_KEY = stringPreferencesKey("lastfm_key") val KEY_LASTFM_KEY = stringPreferencesKey("lastfm_key")
val KEY_BACKEND_URL = stringPreferencesKey("backend_url") val KEY_BACKEND_URL = stringPreferencesKey("backend_url")
val KEY_SCROBBLE_URL = stringPreferencesKey("scrobble_url") val KEY_SCROBBLE_URL = stringPreferencesKey("scrobble_url")
// Adresse der eigenen NovaSonic-Web-App — Basis der geteilten Links.
val KEY_WEB_URL = stringPreferencesKey("web_url")
val KEY_SCROBBLE_MODE = stringPreferencesKey("scrobble_mode") val KEY_SCROBBLE_MODE = stringPreferencesKey("scrobble_mode")
val KEY_SHOW_COUNT = booleanPreferencesKey("show_play_count") val KEY_SHOW_COUNT = booleanPreferencesKey("show_play_count")
val KEY_THEME = stringPreferencesKey("theme") val KEY_THEME = stringPreferencesKey("theme")
@@ -44,7 +42,6 @@ class PrefsStore(private val context: Context) {
val lastFmKey: String = "", val lastFmKey: String = "",
val backendUrl: String = "", val backendUrl: String = "",
val scrobbleUrl: String = "", val scrobbleUrl: String = "",
val webUrl: String = "",
val scrobbleMode: String = "30s", val scrobbleMode: String = "30s",
val showPlayCount: Boolean = false, val showPlayCount: Boolean = false,
val theme: String = "dark-ocean", val theme: String = "dark-ocean",
@@ -76,7 +73,6 @@ class PrefsStore(private val context: Context) {
lastFmKey = prefs[KEY_LASTFM_KEY] ?: "", lastFmKey = prefs[KEY_LASTFM_KEY] ?: "",
backendUrl = prefs[KEY_BACKEND_URL] ?: "", backendUrl = prefs[KEY_BACKEND_URL] ?: "",
scrobbleUrl = prefs[KEY_SCROBBLE_URL] ?: "", scrobbleUrl = prefs[KEY_SCROBBLE_URL] ?: "",
webUrl = prefs[KEY_WEB_URL] ?: "",
scrobbleMode = prefs[KEY_SCROBBLE_MODE] ?: "30s", scrobbleMode = prefs[KEY_SCROBBLE_MODE] ?: "30s",
showPlayCount = prefs[KEY_SHOW_COUNT] ?: false, showPlayCount = prefs[KEY_SHOW_COUNT] ?: false,
theme = prefs[KEY_THEME] ?: "dark-ocean", theme = prefs[KEY_THEME] ?: "dark-ocean",
@@ -112,7 +108,6 @@ class PrefsStore(private val context: Context) {
prefs[KEY_LASTFM_KEY] = settings.lastFmKey prefs[KEY_LASTFM_KEY] = settings.lastFmKey
prefs[KEY_BACKEND_URL] = settings.backendUrl prefs[KEY_BACKEND_URL] = settings.backendUrl
prefs[KEY_SCROBBLE_URL] = settings.scrobbleUrl prefs[KEY_SCROBBLE_URL] = settings.scrobbleUrl
prefs[KEY_WEB_URL] = settings.webUrl.trimEnd('/')
prefs[KEY_SCROBBLE_MODE] = settings.scrobbleMode prefs[KEY_SCROBBLE_MODE] = settings.scrobbleMode
prefs[KEY_SHOW_COUNT] = settings.showPlayCount prefs[KEY_SHOW_COUNT] = settings.showPlayCount
prefs[KEY_THEME] = settings.theme prefs[KEY_THEME] = settings.theme
@@ -609,11 +609,11 @@ fun AppNavigation() {
contextMenuSong = null contextMenuSong = null
}, },
// Link auf die eigene Web-App, den der Empfaenger mit SEINEN Zugangsdaten oeffnet — // Link auf die eigene Web-App, den der Empfaenger mit SEINEN Zugangsdaten oeffnet —
// uebertragen wird nur die Song-Id, kein Geheimnis. Ohne konfigurierten Host waere // uebertragen wird nur die Song-Id, kein Geheimnis. Der Host kommt aus BuildConfig
// der Link unbrauchbar, deshalb erscheint der Eintrag dann gar nicht erst. // (dieselbe Angabe wie fuer die App-Links im Manifest), damit es keine vierte
onShare = appSettings.webUrl.takeIf { it.isNotBlank() }?.let { host -> // Adresse zum Pflegen gibt und beide Richtungen nie auseinanderlaufen koennen.
{ onShare = {
val link = "${host.trimEnd('/')}/?song=${song.id}" val link = "https://${com.novasonic.app.BuildConfig.SHARE_HOST}/?song=${song.id}"
val text = listOfNotNull(song.title, song.artist).joinToString(" — ") val text = listOfNotNull(song.title, song.artist).joinToString(" — ")
context.startActivity(android.content.Intent.createChooser( context.startActivity(android.content.Intent.createChooser(
android.content.Intent(android.content.Intent.ACTION_SEND).apply { android.content.Intent(android.content.Intent.ACTION_SEND).apply {
@@ -621,7 +621,6 @@ fun AppNavigation() {
putExtra(android.content.Intent.EXTRA_SUBJECT, text) putExtra(android.content.Intent.EXTRA_SUBJECT, text)
putExtra(android.content.Intent.EXTRA_TEXT, "$text\n$link") putExtra(android.content.Intent.EXTRA_TEXT, "$text\n$link")
}, "Song teilen")) }, "Song teilen"))
}
}, },
// Nur mit Backend: das Loeschen laeuft ueber /api/delete-song, Navidrome allein // Nur mit Backend: das Loeschen laeuft ueber /api/delete-song, Navidrome allein
// kann keine Dateien entfernen. // kann keine Dateien entfernen.
@@ -210,12 +210,6 @@ fun SettingsScreen(onOpenStats: () -> Unit = {}, onOpenCompare: () -> Unit = {})
) )
ConnStatusRow(status = state.scrobbleStatus, label = "Nicht konfiguriert", onTest = vm::testScrobble) ConnStatusRow(status = state.scrobbleStatus, label = "Nicht konfiguriert", onTest = vm::testScrobble)
NsTextField(
value = state.webUrl, onValueChange = vm::onWebUrl,
label = "NovaSonic Web-Adresse",
hint = "Basis der geteilten Links, z. B. https://music.example.com",
keyboardType = KeyboardType.Uri,
)
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
@@ -30,7 +30,6 @@ data class SettingsUiState(
val lastFmKey: String = "", val lastFmKey: String = "",
val backendUrl: String = "", val backendUrl: String = "",
val scrobbleUrl: String = "", val scrobbleUrl: String = "",
val webUrl: String = "",
val scrobbleMode: String = "30s", val scrobbleMode: String = "30s",
val showPlayCount: Boolean = false, val showPlayCount: Boolean = false,
val theme: String = "dark-ocean", val theme: String = "dark-ocean",
@@ -63,7 +62,6 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
lastFmKey = s.lastFmKey, lastFmKey = s.lastFmKey,
backendUrl = s.backendUrl, backendUrl = s.backendUrl,
scrobbleUrl = s.scrobbleUrl, scrobbleUrl = s.scrobbleUrl,
webUrl = s.webUrl,
scrobbleMode = s.scrobbleMode, scrobbleMode = s.scrobbleMode,
showPlayCount = s.showPlayCount, showPlayCount = s.showPlayCount,
theme = s.theme, theme = s.theme,
@@ -81,7 +79,6 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
fun onLastFmKey(v: String) = update { it.copy(lastFmKey = v, savedOk = false) } fun onLastFmKey(v: String) = update { it.copy(lastFmKey = v, savedOk = false) }
fun onBackendUrl(v: String) = update { it.copy(backendUrl = v, savedOk = false, backendStatus = ConnStatus.Idle) } fun onBackendUrl(v: String) = update { it.copy(backendUrl = v, savedOk = false, backendStatus = ConnStatus.Idle) }
fun onScrobbleUrl(v: String) = update { it.copy(scrobbleUrl = v, savedOk = false, scrobbleStatus = ConnStatus.Idle) } fun onScrobbleUrl(v: String) = update { it.copy(scrobbleUrl = v, savedOk = false, scrobbleStatus = ConnStatus.Idle) }
fun onWebUrl(v: String) = update { it.copy(webUrl = v, savedOk = false) }
fun onScrobbleMode(v: String) = update { it.copy(scrobbleMode = v, savedOk = false) } fun onScrobbleMode(v: String) = update { it.copy(scrobbleMode = v, savedOk = false) }
fun onShowPlayCount(v: Boolean) = update { it.copy(showPlayCount = v, savedOk = false) } fun onShowPlayCount(v: Boolean) = update { it.copy(showPlayCount = v, savedOk = false) }
fun onAbsUrl(v: String) = update { it.copy(absUrl = v, savedOk = false, absStatus = ConnStatus.Idle) } fun onAbsUrl(v: String) = update { it.copy(absUrl = v, savedOk = false, absStatus = ConnStatus.Idle) }
@@ -174,7 +171,6 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
lastFmKey = s.lastFmKey.trim(), lastFmKey = s.lastFmKey.trim(),
backendUrl = s.backendUrl.trimEnd('/'), backendUrl = s.backendUrl.trimEnd('/'),
scrobbleUrl = s.scrobbleUrl.trimEnd('/'), scrobbleUrl = s.scrobbleUrl.trimEnd('/'),
webUrl = s.webUrl.trimEnd('/'),
scrobbleMode = s.scrobbleMode, scrobbleMode = s.scrobbleMode,
showPlayCount = s.showPlayCount, showPlayCount = s.showPlayCount,
theme = s.theme, theme = s.theme,