Secretary

Apps/launcher/changelog
Login

Apps/launcher/changelog

Secretary Launcher (Android) - Changelog

Every released version of the Secretary Launcher app, newest first.

App documentation: Apps/launcher

Status

Status 2026-10-02 (466)

Secretary Launcher 466 (versionCode 466): die Ausrichtung der Home-Slots tat wieder nichts, und der Drawer zeigt jetzt das Wallpaper unscharf.

(1) Ausrichtung. In 465 hat die Spalte ihre Position ueber Modifier.align(...) auf ihrem eigenen Widget bekommen, mit Alignment.CenterStart/Center/CenterEnd. Das ist genau die Falle, in der die Einstellung unsichtbar wird: sobald das Widget die volle Breite einnimmt (der Fall, in dem die Slots ohnehin alle gleich aussehen), sind alle drei Ausrichtungen pixelgleich — und die Namen blieben zentriert, weil die Column im Widget horizontalAlignment = CenterHorizontally hatte. Der Widget selbst darf aber gar nicht die Anordnung tragen: er muss verschiebbar sein, und ein vollbreites Widget laesst sich gar nicht seitlich bewegen. Die Anordnung sitzt jetzt wieder ausserhalb, als Box um das Widget herum (fillMaxSize + 20/112/48 dp, contentAlignment = dieselben drei Ausrichtungen), und die Ausrichtung wirkt zusaetzlich innerhalb der Spalte (slotAlignment = Start/CenterHorizontally/End). Fuer alle drei Einstellungen ist das Bild damit wieder genau das der alten homeAppsLayout: alle Zeilen linksbuendig ab 20 dp, mittig oder rechtsbuendig bis 20 dp vor dem Rand.

(2) Drawer mit unscharfem Wallpaper. Neu ist ui/Wallpaper.kt mit BlurredWallpaper(scrim): WallpaperManager.getDrawable() (seit API 24 ohne Permission, minSdk ist 26) wird auf 1/32 der Bildschirmbreite gezeichnet, auf 1/8 gestreckt und von dort bilinear ueber den Bildschirm gezogen — dieses Runter-/Hochskalieren ist der Weichzeichner, kostet ein paar Millisekunden und braucht weder RenderEffect (nur API 31+) noch RenderScript (weg), und der Speicher springt nicht um die Groesse eines Wallpapers. Zwei Durchgaenge, weil ein einzelner bilinearer Durchgang sichtbare Nahtstellen hinterlaesst. Der Drawer bekommt das als erste Ebene, darueber ein neu translazenter Scrim (LocalLauncherWallpaperScrim, 0xD9 statt 0xF2) — Blur allein ist kein Kontrast, auf einem hellen Foto verschwinden die App-Namen sonst. Ohne Wallpaper (oder wenn ein Build die Berechtigung verweigert) bleibt der Scrim allein, also das alte Verhalten. Der Einstellungs-Screen bleibt unberuehrt.

Tests: keine neuen — beide Aenderungen sind Kompositions-Glue und Farbentscheidungen, fuer die es im Modul keine JVM-Tests gibt (kein Robolectric). 674 Tests gruen ueber gradle test, keine Warnung in einer geaenderten Datei (kein @Suppress, kein getFastDrawable-Fallback). versionCode 465 -> 466.

Status 2026-10-02 (465)

Secretary Launcher 465 (versionCode 465): die Home-Slots sind ein Widget. In 463/464 war jeder Slot fuer sich verschiebbar (app-1 … app-8, neun Positionen fuer zwei Dinge); sie fielen dabei auch aus ihrer Spalte heraus und standen einzeln irgendwo. Jetzt ist die ganze Spalte ein Widget: WidgetPosition.KEYS kennt nur noch clock und apps, AppsWidget in HomeScreen.kt kapselt alle Slots (inklusive Leere-Hinweis) in einem MovableWidget mit einer hugenden Column - die Reihenfolge bleibt, ein Zug verschiebt alles, und nichts kann sich von der Gruppe loesen.

Die alte Anordnung ist die Vorgabe, nicht eine Naeherung. Unbewegt sitzt die Gruppe genau wie vorher im Padding der alten homeAppsLayout (20 dp links/rechts, 112 dp oben, 48 dp unten, vertikal zentriert); nur ist das Widget nicht mehr fillMaxSize mit Arrangement.Center, sondern eng (hugging) und wird ueber Modifier.align(...) in genau diese Box gestellt. Dafuer genuegen die drei Ausrichtungen, die Compose ohnehin mitbringt und die die Spalte in beiden Richtungen kennen: Alignment.CenterStart/Center/CenterEnd aus homeAlignment (vorher Gravity-Start/Center/End, mit LayoutDirection-Aufloesung wie Gravity). Bewegt liegt die Gruppe frei an der abgelegten Stelle, geklemmt gegen die gemessene Gruppengroesse - die ist mit Textgroesse und Label-Laenge wachsend. Ein eigener Alignment war dafuer nicht noetig (und der erste Versuch scheiterte an Alignment.Horizontal.align, dessen Signatur in compose-ui 1.12 nicht die von 1.6 ist).

Der Bruch mit 463/464 ist absichtlich, aber nicht still. Die app-<n>-Positionen werden verworfen (der Serverstand mit ihnen, fromJson verwirft unbekannte Schluessel), eine bestehende Installation landet also zunaechst in der Default-Anordnung, bis der Nutzer die Gruppe zieht. Wichtig war dabei die Schluesselhygiene: Prefs.POSITION_KEYS enthaelt die alten HOME_POS_app-<n>_X|_Y weiterhin, sonst haette der Datei-Scan in allRenameLabels() sie fuer Paketnamen gehalten und der Drawer einen umbenannten App-Eintrag mit dem Label "0.42" gezeigt; clearWidgetPositions entfernt sie beim Startbildschirm zuruecksetzen mit. Sync-JSON ist damit "clock"/"apps" statt "app-<n>".

Tests: WidgetPositionTest prueft jetzt KEYS == {clock, apps} und dass app-1/app-8 keine Widget-Schluessel mehr sind (der Test fuer appSlotOf ist mit der Funktion entfallen), LauncherSettingsTest foreignBrokenAndLegacyWidgetKeysAreDropped traegt die app-1/app-8-Keys ausdruecklich als "server carries them, they must not resurrect a layout" ein. 674 Tests gruen ueber gradle test (Kalender 55, common 100, Kontakte 44, Launcher 24, Secrets 405, Tasks 46), in keiner geaenderten Datei eine Warnung. Die neue Gruppierung selbst ist Kompositions-Glue und hat wie der Fehler aus 464 keinen JVM-Test. versionCode 464 -> 465.

Status 2026-10-02 (464)

Secretary Launcher 464 (versionCode 464): das Ziehen der Uhr ging nicht — der Bildschirm-Ursprung war um ein Bildschirm zu weit verschoben. Ein Einzeiler in HomeScreen.kt: der Home-Box wurde als Ursprung positionInRoot() - Offset(width, height) eingetragen. positionInRoot() ist schon die linke obere Ecke des Knotens (belegt im Bytecode von compose-ui 1.12.1: positionInRoot(c) = c.localToRoot(Offset.Zero), und localToRoot ist calculateLocalPosition(offset) - root.positionInRoot()), das Abziehen der Groesse hat den Ursprung also auf (-W, -H) gesetzt. Damit lag die Layout-Position jedes Widgets um (+W, +H) daneben und damit immer jenseits der Klemmgrenze. Zwei Symptome, ein Fehler: beim ersten Ziehen schnappte moveWithin das Widget in die untere rechte Ecke, statt es dem Finger folgen zu lassen (unbeherrschbar), und ein bereits verschobenes Widget bekam eine Verschiebung, die sich exakt aufhob — es bewegte sich gar nicht mehr. Der Ursprung ist jetzt it.positionInRoot(), der Kommentar an beiden Stellen sagt die Invariante. Sonst aendert sich nichts: die Widget-Positionen in den Einstellungen, die Reihenfolge im Drawer und der Sync bleiben wie in 463.

Keine neuen Tests, und das ist der Punkt: der Fehler lag im Compose-Glue zwischen onGloballyPositioned und moveWithin, nicht in der Rechnerei - die 25 Launcher-Tests sind gruen und waren es vorher auch. Das Modul hat kein Robolectric und keine Compose-UI-Tests, genau an dieser Stelle ist also nichts abgesichert. 675 Tests gruen ueber gradle test, alle neun Module kompilieren mit compileDebugKotlin, in keiner geaenderten Datei eine Warnung. 463 war veroeffentlicht; caern-repo-manager haelt je Paket nur die aktuelle Version (~/Apks, keep_apk_versions greift fuer APKs offenbar nicht), der Import von 464 hat 463 also ersetzt. versionCode 463 -> 464.

Status 2026-10-02 (463)

Secretary Launcher 463 (versionCode 463): Uhr und Home-Apps sind verschiebbar, das Drawer-Suchfeld sitzt unten. (1) Neu ist data/WidgetPosition.kt: eine Position als Bruchteil des Bildschirms (nicht in Pixel, sonst waere sie nach einer Rotation oder auf einem anderen Geraet falsch) plus die reine Rechnerei offsetOf, moveWithin, fractionOf. Die drei klemmen gegen die gemessene Widgetgroesse, nicht gegen die Bildschirmgroesse - ein Widget darf nicht zur Haelfte aus dem Bild rutschen, wenn der Text groesser oder das App-Label laenger wird. Prefs bekommt widgetPosition, setWidgetPosition, widgetPositions, hasWidgetPositions und clearWidgetPositions; die Schluessel HOME_POS_<widget>_X|_Y stehen in POSITION_KEYS und damit in FIXED_KEYS, sonst haette der Datei-Scan sie fuer Rename-Labels gehalten. HomeScreen.kt hat MovableWidget bekommen: bis der Nutzer zieht, hat ein Widget ueberhaupt keine Position und liegt damit unveraendert in der alten Anordnung (Uhr oben, Ausrichtung = homeAlignment; die Slots zentriert in der Spalte mit 20/112/48 dp) - eine bestehende Installation sieht also genauso aus wie vorher. Waehrend des Ziehens folgt das Widget dem Finger als graphicsLayer-Translation ueber seiner Layout-Position (kein Pref-Write pro Frame, keine Recomposition), geschrieben wird genau ein Wert, wenn der Finger loslaesst; danach rendert das Widget als Geschwister neben der Anordnungsspalte und setzt sich mit Modifier.offset aus seiner Position. Die neue widgetDrag-Erkennung in Gestures.kt verbraucht die Bewegung, damit das Wischen nach oben weiter den Drawer oeffnet, und laesst Klick und Long-Press an den Slot darunter. AppDrawerScreen.kt hat das Suchfeld von oben (neben dem Zurueck-Pfeil, wo es die Tastatur verdeckte) unter die App-Liste gesetzt, dazu imePadding() auf die Spalte. SettingsScreen.kt hat die Zeile Startbildschirm zuruecksetzen (MainViewModel.resetWidgetPositions(), EN+DE). Und: die Einstellungen tragen jetzt wirklich jede Einstellung. LauncherSettings erhaelt widgetPositions (JSON-Objekt "clock"/"app-<n>" zu [x, y], unbekannte Schluessel fallen raus, Werte werden auf 0..1 geklemmt), und der KDoc in LauncherSettings und Prefs sagt, warum das keine Option ist: applyRemoteState leert die Datei, bevor der Serverstand hineingeschrieben wird - eine Einstellung ohne Feld im LauncherSettings waere beim naechsten Pull weg. Ausgenommen bleiben die zwei geraetelokalen Flags (FIRST_OPEN bleibt stehen, FIRST_SETTINGS_OPEN wird zu false).

Tests: 13 neu, 675 gruen, verifiziert ueber gradle :launcher:testDebugUnitTest; vier in LauncherSettingsTest (Round-Trip, unberuehrte Installation ohne Positionen, fremde und kaputte Schluessel fallen raus, Positionen ausserhalb des Bildschirms werden zurueckgezogen) und WidgetPositionTest (Offset, Klemmung an den Raendern, nach wachsendes Widget, Uebergroesse, Ziehen, Ziehen an den Raendern, Umkehrung Fraction -> Pixel, Schluessel clock/app-<n>, Slotnummern). versionCode 462 -> 463.

Status 2026-10-01 (462)

Secretary Launcher 462 (versionCode 462): die Sync-Aktion ist ohne Konto weg, Passwortfelder zeigen Klartext. Beides sitzt im gemeinsamen Code und trifft damit alle acht Clients.

(1) Keine Sync-Aktion ohne Konto. AppHeader (Kit.kt) blendet die Sync-Aktion bereits aus, wenn onSync null ist - das war die vorhandene Konvention, nur wurde sie an keiner Stelle benutzt. OfflineBanner (StatusBanners.kt) verlangte dagegen einen nicht-nullbaren onSync und zeichnete den Refresh-Button bedingungslos; der Parameter ist jetzt (() -> Unit)?, und ohne Konto fehlt der Knopf (die Meldung "offline" bleibt stehen). Alle Aufrufstellen reichen den Callback nur noch durch, wenn wirklich ein Konto existiert: loggedIn der jeweiligen ViewModel, im Kalender der collected isLoggedIn-StateFlow. Das Journal ist der einzige Client, bei dem das sichtbar war - es hat keine Login-Sperre und laeuft im lokalen Modus (localMode = !repo.isConfigured()) ohne Konto weiter, zeigte den Sync-Knopf aber trotzdem. Beim Secrets-Tresor war der Knopf vorhanden und nur deaktiviert (enabled = state.connected); er wird jetzt gar nicht erst gezeichnet, der Spinner waehrend state.syncing bleibt. Am Launcher aendert sich nichts: die Server-Sync-Sektion im Settings-Screen war schon an account != null gehaengt.

(2) Passwortfelder zeigen Klartext. Alle Eingabefelder fuer Passwoerter nehmen VisualTransformation.None; die Tastatur bleibt KeyboardType.Password. Betroffen sind der gemeinsame Login-Screen (SecretaryLoginScreen), die Journal-Passwortdialoge (PasswordDialogs: Setzen, Aendern, Abfragen) und das Entschperren (UnlockScreen) sowie im Tresor die Felder fuer Recovery-Passphrase, Entschperr-Passphrase, KDBX-Import und KDBX-Export (SecretsApp) und das Passwortfeld im Eintrags-Editor (Browse). Anlass war die Darstellung, nicht die Sicherheit: die Maske besteht aus U+25CF (●), und die gebundelte Typewriter-Schrift Special Elite deckt dieses Zeichen nicht ab - die Felder zeigten unlesbare Ersatzglyphen. In den Tresor-Dialogen ist der Sichtbarkeits-Umschalter damit ueberfluessig und mit ihm die Felder passwordVisible/visible, der Eye-Button bzw. der Textbutton Anzeigen/Verbergen und die Visibility/VisibilityOff-Imports weg. Der Reveal-Pfad fuer gespeicherte Secrets in Browse (FieldRow) bleibt unberuehrt: das ist eine Anzeige, keine Eingabe.

Tests: keine neuen, kein JVM-Verhalten geaendert. 662 Tests gruen (Kalender 55, common 100, Kontakte 44, Launcher 12, Secrets 405, Tasks 46), verifiziert ueber gradle test; alle neun Module kompilieren (compileDebugKotlin, offline), und die Neuuebersetzung meldet in keiner der geaenderten Dateien eine Warnung. Am Launcher-Code aendert sich keine Zeile; er teilt nur die beiden gemeinsamen Aenderungen (der Login-Screen und OfflineBanner). versionCode 461 -> 462.

Status 2026-09-30 (461)

Secretary Launcher 461 (versionCode 461): die Namensraum-Karte im Einstellungs-Screen ist weg, die Version steht jetzt unten. Beides steckt im gemeinsamen SecretarySettingsScreen in common (SRS common 1.3.0), trifft also alle acht Clients. Der Parameter collectionNamespace und die Karte Namensraum sind raus - sie zeigte eine Konstante, die der Nutzer ohnehin in der Collection-Liste sieht. Die Konstante LauncherRepository.NAMESPACE bleibt als CalDAV-Basispfad. Neu ist die letzte Zeile des Screens: die Version der jeweils laufenden App aus der PackageInfo des eigenen Pakets, jede App zeigt so ihre eigene Nummer, ohne sie durchzureichen. Am Launcher-Code ändert sich sonst nichts. versionCode 460 -> 461.

Status 2026-09-30 (460)

Secretary Launcher 460 (versionCode 460): Auslieferung der Korrektur am Konfigurations-Sync, die in 459 beschrieben ist. Gegenüber 459 ändert sich am Code nichts: 459 wurde nie veröffentlicht. Zu dem Zeitpunkt lagen die übrigen sieben APKs und beide 0.23.0-Pakete bereits mit ihren Versionen im Archiv, und das Buildsystem überspringt einen Lauf, dessen Zieldateinamen alle schon dort sind — ohne den Sprung auf 460 wäre also gar nichts erschienen. Deshalb geht der Release-Zug alle acht Clients und beide Serverpakete (0.24.0) zusammen hoch. Der Fix selbst unverändert: onLocalChange() rief nach der Debounce-Zeit run() auf, den vollständigen Sync, der zuerst den Serverstand übernimmt; eine lokal gewechselte App im Home-Slot sprang damit nach 1,5 Sekunden auf den alten Wert zurück. Der debounced Pfad schiebt jetzt über push() direkt den lokalen Stand, ohne vorher zu lesen. versionCode 459 -> 460.

Status 2026-09-30 (459)

Secretary Launcher 459 (versionCode 459): eine Auswahl im Home-Slot bleibt stehen. Der Slot einer angepinnten App ließ sich nicht ändern: man wählte im Drawer eine andere App, und der Slot sprang nach gut einer Sekunde auf den alten Wert zurück. Ursache ist nicht die Speicherung, sondern die Reihenfolge im Sync. Jede lokale Änderung meldet MainViewModel über prefs.onWrite an LauncherSync.onLocalChange(), und das wartete die Debounce-Zeit und rief dann run() auf — dieselbe Funktion wie ein vollständiger Sync. Die holte zuerst den Serverstand und übernahm ihn, sobald er sich vom lokalen unterschied (run(), if (remote != null && remote != …) prefs.applyRemoteState(merged)). Die lokale Auswahl war damit 1,5 Sekunden alt, als der Pull den alten Servereintrag zurückschrieb — und writeRemote() schob genau den wieder zum Server hoch. Der Server gewann also dauerhaft, und keine lokale Änderung konnte sich je durchsetzen. Der Doku-Kommentar an LauncherSync versprach das Gegenteil („Local edits are debounced and pushed without a preceding pull“), der Code machte es nicht. Jetzt gibt es den debounced Pfad als push(): er schreibt den lokalen Stand mit LauncherSettings.fromPrefs(prefs) direkt zum Server und liest dabei nichts. Ein vollständiger Sync (syncNow() bei App-Start, Login, von Hand) behält die alte Semantik „Server gewinnt“. versionCode 458 -> 459.

Status 2026-09-29 (458)

Secretary Launcher 458 (versionCode 458): Einstellungen zweizeilig – Icon + Text in Zeile 1, Bedienelement in Zeile 2. Die Chip-Reihen (Thema, Textgröße, Home-Textfarbe, Datum/Uhrzeit, Home-Stil, Ausrichtung, Tastatur) stand im SettingRow neben Icon und Text in einer Zeile; weil die Chips ihre Breite nicht abgeben, blieb für die Textspalte nur ein Rest – der Text umbrach bis auf einen Buchstaben pro Zeile. Neu ist im gemeinsamen Kit (android/common/.../ui/Kit.kt) das SettingRowStacked: die erste Zeile bleibt Icon + Titel + Subtitle über die volle Breite, das Bedienelement rutscht in eine zweite Zeile, um die Icon-Spalte (38 + 12 dp) eingerückt. Der bisherige SettingRow bleibt unverändert (die anderen Apps hängen dort nur schmale Schalter/Textfelder an) und nutzt jetzt dieselben ICON_BOX/ICON_GAP-Werte wie die gestapelte Variante. Die Chip-Reihen des Launchers laufen als FlowRow, damit die sieben Textgrößen-Chips umbrechen statt die Zeile zu verbreitern. versionCode 457 -> 458.