60 lines
7.9 KiB
Markdown
60 lines
7.9 KiB
Markdown
# Roadmap
|
||
|
||
Foto Dup Finder is built against a fixed milestone plan (**M0–M10**). This file is the
|
||
single source of truth for what is done and what remains. Before this file existed the
|
||
plan lived only in developer notes, and the [CHANGELOG](../CHANGELOG.md) refers to
|
||
milestone labels (`M7`, `M8`).
|
||
|
||
Legend: ✅ done · 🚧 in progress · ⏳ not started · 💡 candidate (proposed, not scheduled)
|
||
|
||
## Milestones
|
||
|
||
| # | Milestone | Status | Evidence / notes |
|
||
|---|---|---|---|
|
||
| M0 | Toolchain & scaffold | ✅ | User-local SDK, Gradle 9.3.1 + AGP 9.1.0, wrapper and version catalog, empty debug APK builds |
|
||
| M1 | Pure-Kotlin duplicate engine + JVM tests | ✅ | SHA-256 quick/full hashing, pHash + RGB dHash, union-find grouping, Exact/Balanced/Aggressive policies; tests under `app/src/test` |
|
||
| M2 | Data layer | ✅ | MediaStore query, coroutine-parallel hashing, incremental SQLite hash index, permission + partial-access handling |
|
||
| M3 | Compose UI | ✅ | Scan progress, grouped results with thumbnails, two-step staging, selection helpers |
|
||
| M4 | Safe deletion | ✅ | `MediaStore.createDeleteRequest` / `createTrashRequest`; legacy direct delete on Android 10 |
|
||
| M5 | Performance pass | ✅ | Size short-circuit, provider-thumbnail hashing, batched hashing (folded into M2/M3) |
|
||
| M6 | Release config & Play readiness | ✅ | R8/minify, optional signing, `.aab`, [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md), [PRIVACY.md](../PRIVACY.md), README |
|
||
| M7 | On-device validation & hardening | ✅ | Foreground `dataSync` service, one failure boundary per file, `core/DecodeMath`, hostile fixtures ([EDGE_CASE_TESTING](EDGE_CASE_TESTING.md)), `versionName` → 1.0.0 |
|
||
| M8 | Localisation groundwork | ✅ | Every user-visible string in `res/values/strings.xml` with real `<plurals>`, shared `text/TextRes.kt`; **English is the only locale** |
|
||
| M9 | Monetisation | ⏳ | No billing code. Blocked on a product decision (free forever vs. one-time unlock vs. limited free scans); changes the Data Safety answers |
|
||
| M10 | Store assets | ✅ | `tools/make-store-assets.sh` renders the 512×512 icon and the 1024×500 feature graphic from the app's own vectors; `tools/make-store-screenshots.sh` normalises device captures to Play's 2:1 limit (1080×2160); `tools/make-demo-photos.py` generates the photo library the results capture is taken over, so no real photos are published; all listing copy is drafted and length-checked in [STORE_LISTING](STORE_LISTING.md). Four screenshots (empty, scanning, results, Options) were re-shot after a real permission grant and normalised to 1080×2160; screenshots are deliberately not committed |
|
||
|
||
## Release blockers (work that is not a numbered milestone)
|
||
|
||
| Item | Detail |
|
||
|---|---|
|
||
| ~~Large-library scan stall~~ ✅ **fixed — verified on device** | The 5,363-photo library used to freeze at *“74 / 100 · Comparing file signatures…”* (a **blocked syscall**: zero CPU, workers sleeping). `BoundedCall` now abandons a stuck provider call **and** trips a stall breaker after three consecutive timeouts. **Verified on the real test device (Android 12 / MIUI): the scan completes — 20.2 s cold, 11.0 s warm, 15 groups, 6 unreadable — where it used to hang.** Covered by `BoundedCallTest` and `ScanEngineEndToEndTest` |
|
||
| ~~Unreleased `BoundedCall` fix~~ ✅ | The provider-call deadline shipped in **1.0.1** (`versionCode` 2) |
|
||
| **Play Console: foreground-service declaration** | Declaring the `dataSync` type (App content › Foreground service permissions) is release-blocking. **[code]** the manifest already declares it; the ready-to-paste justification is in [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) step 5 — this is a Console action, not a code change |
|
||
| **Remaining Play paperwork** | Console actions only, no code: the **first upload + Play App Signing enrolment**, the **hosted privacy policy URL** (the working copy is now published to Gitea as [`fishfacegit/foto-duplicate-finder`](https://gitea.softwareapp-hb.de/fishfacegit/foto-duplicate-finder), so the policy renders at a public, login-free URL and only the Console paste is left), **Data safety**, the **Photo/video permissions** declaration, the **content rating / audience / app access / ads / export** questions, and the **listing copy + four screenshots**. Keystore, `versionCode`, listing copy, graphics, permission audit and the signed `.aab` (`sha256 f8e6a29a…`) are all in place; rollout goes internal → closed → production. Step-by-step: [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) |
|
||
| ~~Store screenshots need one re-shoot~~ ✅ **done — verified on device** | The old captures showed the *Photo access needed* banner (an app-op is not a grant) and `03-results.png` showed the developer's own library. After a real grant the set was re-shot over the generated demo library: **01-empty** (no tap needed — results and selection live in `ScannerViewModel`, so force-stop + relaunch returns to the empty state with the grant still in place), **02-scanning** (`5353 / 5385`), **03-results** (**17 groups, 54 of 5385 photos, 83.5 MB reclaimable**; the headline group's 43.1 MB out-ranks the device's own 39.8 MB, so no real photo is in frame) and **04-options**. Captures are taken by watching for UI **text** changes, because MIUI refuses synthetic taps (`INJECT_EVENTS`). The demo library is deleted from the test device afterwards — MediaStore **5,386 → 5,364** rows, and the 22 stale rows it leaves in the hash index are pruned by `PhotoIndexStore.pruneTo` on the next scan. Deliberately not captured: the staged state and the system confirmation dialog, because staging clears the demo group and would put real photos back in frame. Details: [STORE_LISTING](STORE_LISTING.md) |
|
||
| **Device-only verifications** | RAW/DNG files, a *real* iPhone HEIC with a non-upright EXIF orientation, permission revoked mid-scan, and the Android 14 “Selected photos” flow. ✅ The **index-reuse** check is done: after a scan the index holds **5,363 rows, 5,357 with a perceptual hash**, and a second scan runs in **11.0 s vs 20.2 s** cold |
|
||
|
||
## Candidate milestones (proposed, not scheduled)
|
||
|
||
| Candidate | Rationale |
|
||
|---|---|
|
||
| **Colour signature** | Flat-colour images (blank scans, dark UI screenshots) hash identically regardless of colour, so they are reported as mutual duplicates — see the “Known gap” in [EDGE_CASE_TESTING](EDGE_CASE_TESTING.md). A small colour histogram is orthogonal to the grayscale structure hash, and the benchmark harness already has an implementation to measure against |
|
||
| **Video support** | Only images are scanned today (README “Known limitations”) |
|
||
| **“Similar shots” section** | Bursts / same-subject shots as a separate, never-pre-selected list. Needs a different algorithm and must not feed the deletion flow |
|
||
| **Warm-scan speed** | A second scan is faster (11.0 s) but its “similar” stage still costs ~10 s even though all 5,357 perceptual hashes are cached — the per-photo `withTimeoutOrNull` wrapper still runs for all 5,363 photos. Profiling this could make an unchanged library re-scan in well under a second |
|
||
|
||
## Suggested order
|
||
|
||
1. ✅ **Done** — the scan-stall hardening and the `BoundedCall` fix shipped as **1.0.1**
|
||
(`versionCode` 2). Only the on-device re-check of the 5,363-photo scan remains.
|
||
2. Finish the Play Console paperwork and roll out through **internal → closed → production**.
|
||
3. ✅ **Done** — **M10 store assets**: the icon and feature graphic are generated from the
|
||
app's own vectors, the screenshot normaliser enforces Play's 2:1 limit, the listing copy is
|
||
drafted and length-checked, and the demo library that keeps real photos out of the results
|
||
capture is generated and validated. The re-shoot after a real permission grant is done as well
|
||
(see blockers): four captures, normalised to 1080×2160.
|
||
4. **Colour signature** — the flat-colour false-positive fix.
|
||
5. **M9 — monetisation**, once the product decision is made.
|
||
|
||
Deliberately deferred: **video support** and the **“similar shots”** section.
|