# Working in this repository **Plain Files** is a file manager for Android: Kotlin and Jetpack Compose, one Gradle module (`app`), `minSdk` 26, `targetSdk` 36, and **no `INTERNET` permission** — "nothing leaves the device" is a shipping requirement of this app, not a slogan. It is sold once (no ads, no subscription, no billing code), and the prose in `docs/` is treated as part of the product. Two habits shape everything here. **The app is driven on a real device** for anything a user can see, and what that proved is written down. **A test name is a sentence** about behaviour, so the suite doubles as the specification. ## Read these two first, and only then the source | file | why | |---|---| | `docs/CODE_MAP.md` | Generated index of the source tree: what each file holds, and every test name in the suite. Start here for "which file is this in?" | | `docs/ROADMAP.md` | Which milestone is done, the key decisions, and the deliberate deferrals | Do **not** read `CHANGELOG.md` front to back — it is long, and it is meant to be grepped by feature name once you know what you are changing. ## Commands ```bash ./gradlew :app:testDebugUnitTest # the gate: 355 JVM tests ./gradlew :app:lintDebug # the gate: Android Lint ./gradlew :app:assembleDebug # then: adb install -r app/build/outputs/apk/debug/app-debug.apk ./gradlew :app:assembleRelease # R8 is on here, and it has hidden real bugs — see Traps python3 tools/make-code-map.py # regenerate docs/CODE_MAP.md after touching source python3 tools/make-code-map.py --check # fails when the map is stale ``` There is **no CI**: those two Gradle tasks are the gate, and whoever changes the code runs them. Add `--offline` when the network is unavailable — the dependency cache is already warm. ## Where things live, and the one rule about direction ``` core/ pure Kotlin, no Android imports: paths, names, kinds, sorting, planning data/ storage backends (all-files access, SAF), volumes, shares, SQLite search/trash/favourites domain/ operations over a source: copy/move/delete/rename, archives, search, duplicates service/ the foreground service that keeps a long operation alive ui/ Compose: browser/, viewer/, usage/, trash/, duplicates/, theme/ text/ domain enum to string resource ``` The dependency direction is `ui → domain → data → core`, and `core/` importing `android.*` or `androidx.*` is a test failure, not a matter of taste: it is what keeps the trickiest logic testable without Robolectric. `docs/CODE_MAP.md` has the per-file detail. ## Non-negotiables Each of these is enforced somewhere, so breaking one fails a test, lint, or a reviewer's first comment. `app/src/test/java/…/ProjectInvariantsTest.kt` holds the source-level ones. - **No new permission, and never `INTERNET`.** The manifest's permission set is asserted exactly; adding one means deciding it, explaining it in the manifest comment and the README, and updating that list deliberately. - **Every user-visible string comes from `strings.xml`.** The debug build enables a pseudo-locale (`values-en-XA`), so untranslated text is visible on screen as unaccented text. - **A name is one path segment.** `core/FileNames.isUsableName` is the only implementation; never inspect `/` yourself. `StoragePath.child("a/b")` resolves *into a subfolder*, so an unguarded call renames or creates something the dialog never offered. - **Nothing is overwritten silently.** Operations refuse rather than destroy; deletes go through the trash (24-hour window) and offer undo; a batch that cannot succeed is refused as a whole rather than performed until it hits the problem. - **Comments explain why, not what.** The house style is a KDoc paragraph per file and type that says what it is for and what was rejected — decisions and limits, not restatements of the code. - **A test name is a sentence about behaviour** (`a batch rename is a single undo step`), because the names are read as the spec. Logic gets behaviour tests; rules about the shape of the project get source-level tests. - **The docs are part of the change.** `CHANGELOG.md` (Added/Changed/Fixed, in the house voice), the `ROADMAP.md` milestone table, the README if a claim changed, and `docs/EDGE_CASE_TESTING.md` for anything only a device could confirm. ## Traps that have cost real time here - **MIUI blocks `adb install` twice over.** Developer options needs *Install via USB*, and each install then waits for a confirmation dialog that times out on its own. A bare `INSTALL_FAILED_USER_RESTRICTED` means the dialog was answered late, not that the APK is bad. - **Never run `assembleDebug` and `adb install` in one parallel batch.** They race, the install takes the previous APK, and the feature you just wrote appears to be missing. - **R8 has hidden real bugs.** A class reached only by reflection, or named only from a resource, is stripped from the release build while debug works perfectly — this happened with `coil-video`, where video frames decoded in debug and quietly fell back to icons for real users. When debug and release disagree, assume the shrinker removed something. - **`adb input text` does not deliver a `/`.** Send `input keyevent 76` instead. The slash is worth testing at all because the field *accepts* it and the dialog is what refuses it. - **Long-press multi-select via `input swipe x y x y 900` works, but not every time** — it has also landed as a plain tap. A flaky gesture is a reason to look at the screen, not to distrust the code. - **The debug build is a separate package** (`…filemanager.debug`), so it installs beside a release build and the two can be compared directly. ## Documentation map — read on demand | file | what it is for | |---|---| | `README.md` | The tour: features, layout, build, and the honest **known limitations** | | `CHANGELOG.md` | The reasoning behind each change. Grep it; do not read it end to end | | `docs/ROADMAP.md` | Milestones with their evidence, the key decisions, the deferrals | | `docs/EDGE_CASE_TESTING.md` | What was verified, on which device or AVD, and what was not | | `docs/PLAY_RELEASE_CHECKLIST.md` | Release only — work it in order | | `docs/COMPETITIVE_ANALYSIS.md`, `docs/MONETISATION.md`, `docs/STORE_LISTING.md` | Product context, pricing, and length-checked listing copy | | `PRIVACY.md` | The policy text the store listing points at | ## Definition of done 1. `:app:testDebugUnitTest` and `:app:lintDebug` green. 2. `python3 tools/make-code-map.py` run, so that `--check` passes. 3. A `CHANGELOG.md` entry in the house voice: what changed, why, what was rejected, and what was verified rather than assumed. 4. Anything a user can see driven on a device or emulator, with the result written into `docs/EDGE_CASE_TESTING.md` — including what was deliberately left untested, and why. 5. A commit whose subject is a sentence about the change and whose body explains the reasoning. The diff already says what changed; the message is there for why it is this way.