2026-11-04 04:42:35 -05:00
2026-10-17 22:57:17 -04:00

app-common

Android modules shared between Bryan's apps, each one directory of this Gradle build.

Module What it is
controls/ Icon glyphs and the buttons around them at one set of sizes, the text field with its label above (LabelledField) or without one (UnlabelledField, taking a screen-reader description instead), the setting rows and pickers, tabs between ways of editing one thing's settings (SettingTabs), a loading spinner for a list still loading (LoadingSpinner), fading text, and which way lists run (ListDirection, with order and arrangement for laying a list out that way). Every app draws these alike; the icon font is the app's own, through LocalGlyphFont.
theme/ The colour scheme (AppTheme): the system's, deepened in the dark, with Catppuccin's accents for colours that mean something; the light/dark setting (Theme, ShownTheme); system bars that follow it; and ActionTone.
bars/ UnderBars: content scrolling under blurred bars at the top and bottom, with the screen's header in whichever the header setting names, the tab row for one, a footer slot for rows such as a selection's actions (always in the bottom bar, above the rest), a strip slot for notices that stay in view (in the header's bar, on the content's side), pull to refresh, and keeping a list at its end.
file-browser/ A browser over any source of directories and files, a file viewer, and the syntax highlighter. See its DESIGN.md.
markdown/ Markdown drawn as a comment or a document: links, lists, tables, highlighted fences, images the app loads. Uses file-browser's highlighter. See its DESIGN.md.
graphs/ Graph widgets drawn from plain numbers, labels and colours: LineChart (gaps, dashed reference lines, cheap at any length), horizontal BarChart with floors and counts, CategoryStrip of events over x, Heatmap, and StatReadout and TextReadout. Styled from theme's Catppuccin accents (graphColors); it knows nothing of where the numbers came from.
rust/ The Rust crate app-common, the same design layer for apps on iris: theme (the colour scheme as AppTheme builds it, from iris's SystemTheme; Catppuccin's accents; ActionTone; the light/dark setting, Theme) and typography (APP_TYPOGRAPHY, and WithTextStyle to set a text to one of its roles). Plain data; an app depends on it by git.

Using a module

An app takes this repository as a git submodule and includes only the modules it uses, as subprojects pointed at their directories -- so it is pinned to the exact commit its submodule names, and nothing is published. In the app's settings.gradle.kts, with the path relative to that build:

include(":file-browser")
project(":file-browser").projectDir = file("../app-common/file-browser")

and implementation(project(":file-browser")) in the module that uses it.

A subproject takes its plugins and libraries from the build including it, so every accessor a module's build script names must be in the app's version catalog under the same name. file-browser uses controls and bars, so an app including it includes all three; between them they name:

  • plugins androidLibrary, composeCompiler, ktfmt
  • libraries compose-runtime, compose-foundation, compose-ui, compose-material3, androidx-activity-compose, androidx-core-ktx
  • for its tests, kotlin-test-junit5, junit-jupiter, junit-platform-launcher (JUnit 6, run on the Platform)
  • haze and haze-blur, for bars

markdown uses file-browser, so an app including it includes all four, and names markdown-renderer besides.

A FileBrowserState is closed by the app that made it. Its scope outlives the screen, so an open indexed file would go on fetching after the browser is gone. Call state.close() in the same composition scope that remembers the state, right beside it, so it runs exactly when the remembered state is dropped:

val state = remember(source, target) { FileBrowserState(…) }
DisposableEffect(state) { onDispose { state.close() } }

A host that keeps the state longer -- in a ViewModel, or a rememberSaveable-backed holder -- calls close() where that is cleared.

theme and bars each use controls. theme needs nothing beyond the list above but the two haze entries, which only bars names. graphs uses theme, so an app including it includes theme and controls too, and it likewise names nothing beyond the list but the haze entries.

Settings the app keeps

Four settings are drawn by these modules but stored by the app: the theme (Theme, in theme), which way lists run, which way settings lists run, and which end of the screen headers sit at (each a ListDirection, in controls, each its own toggle). Each has a holder of the value being drawn -- ShownTheme, ShownListDirection, ShownSettingsDirection and ShownHeaderDirection. The app calls restore(context) before its first frame, which shows the copy this device last drew, then hands every value it learns, from a server or its own storage, to show. That copy is only for the first frame; where the setting really lives is the app's. The app provides three locals around everything it draws: ShownListDirection.direction through LocalListDirection, which the bars' pull to refresh and KeepAtEnd follow; ShownSettingsDirection.direction through LocalSettingsDirection, which SettingsList follows; and ShownHeaderDirection.direction through LocalHeaderDirection, which decides whether UnderBars' header goes in its bottom bar (FromBottom) or its top one (TopDown). A screen passes its title row as header, and keeps top and bottom for what belongs at that edge either way.

AppearanceRows (in theme) draws an Appearance section's shared rows: the theme picker, then the three direction toggles. Each row shows its holder's value and hands a change to its hook -- onTheme, onHeaders, onLists and onSettings -- which defaults to that holder's show. An app that keeps a setting elsewhere, such as on its server, passes what stores it there, and that must end by calling the holder's show. The app can add its own rows in the same section.

SettingsList scrolls itself, from the end it reads from, so a screen must not wrap it in a verticalScroll of its own; it takes the bars' listPadding as contentPadding. Everything on a settings screen goes in a section, form fields included, so it mirrors with the rest. Each direct child of a section is one row, with anything belonging to it (its error, its buttons) grouped in that child, and the section spaces the rows, so a screen draws no spacers.

A choice is a PickerRow (or a bare PickerButton, or GlyphPickerButton). Each lists its options by text, and the button shows the current one by buttonText, which defaults to text and is there for a shorter form -- a commit's short hash on the button, hash and subject in the menu. An option enabled answers false for stays listed, drawn disabled, and cannot be picked; the button is pressable only while some enabled option other than the current one exists. A disabled SwitchRow dims its label and glyph as the switch dims.

ListDirection.order(items) is a list's items top to bottom as that direction draws them -- as given from the bottom, mirrored top-down -- and is its own inverse, so an order rearranged as drawn maps back through it. ListDirection.arrangement(gap) spaces rows gap apart and holds a short list to the end the direction starts from.

bars draws the page behind a screen with the scheme's background unless the app provides LocalPageBackground, a function from the ColorScheme in force to the page's Modifier, and LocalOverPage is applied to what is drawn over that page.

gradle/libs.versions.toml here is the reference for all of them, and is what this build tests with.

Building here

This build exists so each module can be built and tested on its own:

ANDROID_HOME=~/Android/Sdk ./gradlew :file-browser:testDebugUnitTest :markdown:testDebugUnitTest :file-browser:lintDebug :markdown:lintDebug :controls:lintDebug :theme:lintDebug :bars:lintDebug :graphs:testDebugUnitTest :graphs:lintDebug
ANDROID_HOME=~/Android/Sdk ./gradlew ktfmtFormat ktfmtFormatScripts :controls:ktfmtFormat :controls:ktfmtFormatScripts :file-browser:ktfmtFormat :file-browser:ktfmtFormatScripts :markdown:ktfmtFormat :markdown:ktfmtFormatScripts :theme:ktfmtFormat :theme:ktfmtFormatScripts :bars:ktfmtFormat :bars:ktfmtFormatScripts :graphs:ktfmtFormat :graphs:ktfmtFormatScripts

Tests and lint pass, and the formatter changes nothing, before anything is pushed.

S
Description
No description provided
Readme
2.3 MiB
0 Stars 1 Watchers 0 Forks
Languages
Kotlin 71.8%
Rust 28.2%