Secretary Launcher (Android)
Monochrome, wallpaper-focused home screen launcher. Replaces the system home with a clock/date, up to 8 configurable app slots — both freely movable, the slots as one block — a gesture-driven app drawer (swipe up to search, auto-launch on exact match) and long-press settings.
What it does
- Replaces the Android home screen: edge-to-edge wallpaper, a clock/date, and up to 8 app slots
- The clock and the slots can be dragged anywhere; the slots keep their order and move as one block
- Gesture app drawer: swipe up to open, type to search, Enter launches the closest match, Enter on an empty search runs a
!bang web search - Home text as app names or as real app icons; rename, app info and uninstall from the drawer long-press
- Theme (light/dark/system), 7 text sizes, alignment, home text colour, automatic or manual keyboard
- Account and settings sync to the server (
/launcher/); Reset home layout puts everything back - Typewriter-style monochrome UI, single font family
Changelog
Every released version: Apps/launcher/changelog
Screenshots

Features
- Edge-to-edge wallpaper background (transparent window, no bars)
- Typewriter-style UI, single type family (
@font/special_elite) - Digital clock + date (off / time-only / date-only)
- Up to 8 home slots; long-press a slot to pick the app from the drawer
- Clock and app slots can be dragged anywhere (kept in the synced settings, Reset home layout in the settings screen puts them back)
- The home slots are one widget: they keep their order and are dragged as a unit, so they cannot drift apart
- App drawer: swipe up, search field at the bottom above the keyboard, Enter
auto-launches the closest match, background is the desktop wallpaper, blurred
(
ui/Wallpaper.kt) !bang search (web search), profile-aware app list (clone apps)- Rename / app-info / uninstall from the drawer long-press dialog
- Theme (light/dark/system), text size (7 levels), alignment, home text color
- Home apps as names or as real Android app icons (profile-aware, cached)
- Drawer keyboard can be automatic (default) or off
- Account + settings sync to the server (
/launcher/), settings screen shared with all other secretary apps - Crash reports written to
filesDir/crashreports/and Downloads
Architecture
- Package:
org.beowulf.secretary.launcher(single Gradle module:launcher) - Depends on
:common(account, CalDAV client,SecretarySettingsScreen,SecretaryLoginScreen) — this is whyminSdkis 26, not 24 - Jetpack Compose UI (Material3), Navigation Compose
Prefs= reactive SharedPreferences wrapper (revisionStatesnapshot for recomposition)MainViewModel<>getAppsListvia LauncherApps (UserManager profiles), fallback to PackageManager- The app list is read once into
_rawAppList; the drawer only re-applies its search filter to that cache.PackageWatcher(process-wideBroadcastReceiveron theApplication, not the activity — the uninstall happens in another app, so ours is backgrounded) bumps atickStateFlow on everyPACKAGE_ADDED/REMOVED/FULLY_REMOVED/REPLACED/CHANGED, andMainViewModelcollects it intorefreshHome(). Without it a removed app stays in the drawer until the process dies.PACKAGE_ACTIONSandonPackageEvent()are free of Android types soPackageWatcherTestneeds no Robolectric (the module has none). launcherGesturesmodifier: single-pointer swipe/tap/long-press recognizerwidgetDragmodifier: drag recognizer for the home widgets. It consumes the movement (so the swipe-up oflauncherGesturesleaves it alone) and claims neither tap nor long press, which the clickable slot inside keepsWidgetPosition: where a widget sits, as the fractions of the screen its top left corner is at. Fractions, so a layout survives a rotation, a different screen size and the sync;offsetOf/moveWithin/fractionOfdo the px math (and clamp against the measured widget size, which grows with the text size or a longer label). An untouched widget has no position at all, so an existing installation keeps the old arrangement. There are two widgets, not nine: the clock and the whole app column (WidgetPosition.KEYS), so the slots stay together and one drag moves them.MovableWidgetfollows the finger as agraphicsLayertranslation over its layout position and writes one preference when the finger is lifted; a moved widget is rendered as a sibling of the default arrangement instead of inside it. The drag handlers outlive recompositions (pointerInput(Unit)) and therefore read the screen size and the callback throughrememberUpdatedState- Default-launcher switch via RoleManager request +
FakeHomeActivityselector trick
Server sync (/launcher/)
- The launcher syncs settings, not user data. One VJOURNAL collection
(
main) below the top-levellauncher/root holdssettings—SUMMARY=settings,CATEGORIES=launcher,settings,DESCRIPTION= JSON ofLauncherSettings(theme, text size, home count/alignment/text colour, date visibility, home app style, drawer keyboard, rename labels, slot snapshot, widget positions),app-<slot>— one entry per occupied home slot (CATEGORIES=launcher,app-<slot>) with label, package, activity class and profile.
LauncherSync(:launcher:sync) does a full pull+push under a mutex: the server wins on every full sync, a local write is pushed after a 1.5 s debounce, entries of freed slots are deleted, ETags are reused.- The shared account (
AccountResolver, typeorg.beowulf.secretary) is the only credential store; without it the launcher keeps working locally and reportsNotConfigured. KNOWN_NAMESPACE_ROOTSin:common(shared with all apps) needs"launcher", otherwise the safe-delete check inCaldavClientrefuses to delete the entries of freed slots.- The client is built with
baseCollectionPath = "/launcher/"and the default empty collection prefix, so the collection keeps the idmain(/launcher/main/).
Layout
android/launcher/
├── build.gradle.kts
├── proguard-rules.pro
├── LICENSE-SpecialElite.txt # Font license (special_elite)
└── src/main/
├── AndroidManifest.xml
├── java/org/beowulf/secretary/launcher/
│ ├── MainActivity.kt # Entry, edge-to-edge, orientation
│ ├── MainViewModel.kt # account, sync wiring
│ ├── SecretaryLauncherApp.kt # CrashReporter + PackageWatcher install
│ ├── navigation/AppNav.kt # home / settings / login routes
│ ├── data/ # Constants, Prefs, AppModel,
│ │ # LauncherSettings, WidgetPosition,
│ │ # LauncherRepository
│ ├── sync/LauncherSync.kt # /launcher/ pull + push
│ ├── helper/ # Utils, FakeHomeActivity, AppIcons,
│ │ # PackageWatcher
│ ├── crash/CrashReporter.kt
│ └── ui/ # Gestures, theme, screens
└── res/ # values(-night, -de), font, icons
Attachments:
- launcher-settings-sync.png [download] added by beowulf on 2026-10-06 16:03:28. [details]
- launcher-settings.png [download] added by beowulf on 2026-10-06 16:03:28. [details]
- launcher-settings-home.png [download] added by beowulf on 2026-10-06 16:03:28. [details]
- launcher-home.png [download] added by beowulf on 2026-10-06 16:03:27. [details]
- launcher-drawer.png [download] added by beowulf on 2026-10-06 16:03:27. [details]