Initial commit: Foto Dup Finder 1.0.1
An Android duplicate-photo finder. Pure-Kotlin core (SHA-256 + dHash hashing, exact and perceptual union-find grouping, three sensitivity presets) with MediaStore access, a persistent SQLite hash index and a tiered ScanEngine behind a Jetpack Compose UI. No INTERNET permission, no ads, no analytics, no account. Ships versionCode 2 / versionName 1.0.1, carrying the scan-stall hardening (BoundedCall breaker) and verified against a 5,363-photo device library. 80 JVM unit tests, lint clean. Release paperwork is in docs/ (Play checklist, store listing, edge-case testing), the upload-ready Play graphics and screenshots in store-assets/, and the generators for those assets in tools/.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Play Store listing (M10)
|
||||
|
||||
Everything the *Main store listing* page in the Play Console asks for, ready to copy. The
|
||||
graphics are generated from the app's own vectors, so they can be regenerated after any
|
||||
branding change instead of being edited by hand.
|
||||
|
||||
See [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) § 4 for where these fit in the
|
||||
release.
|
||||
|
||||
## Graphics
|
||||
|
||||
Generated into `store-assets/` **inside the repository**, never into `$HOME`. These PNGs are
|
||||
the files that get uploaded to the Play Console, so they are committed and versioned with the
|
||||
release they belong to. Only the uncropped captures the screenshots are cut from
|
||||
(`store-assets/raw-screenshots/`) are git-ignored.
|
||||
|
||||
```bash
|
||||
./tools/make-store-assets.sh # icon + feature graphic -> store-assets/
|
||||
./tools/make-store-screenshots.sh # crop the adb captures -> store-assets/screenshots/
|
||||
```
|
||||
|
||||
| Asset | Play's spec | Result | Source |
|
||||
|---|---|---|---|
|
||||
| App icon | 512×512, 32-bit PNG with alpha, ≤ 1 MB | `store-assets/icon-512.png` — 512×512, RGBA, 6 KB | `tools/store-assets/icon.svg`, mirrored from `app/src/main/res/drawable/ic_launcher_background.xml` + `ic_launcher_foreground.xml` (paths and group transforms are identical, so the store icon *is* the launcher icon) |
|
||||
| Feature graphic | 1024×500, JPEG or 24-bit PNG without alpha | `store-assets/feature-graphic-1024x500.png` — 1024×500, RGB | `tools/store-assets/feature-graphic.svg` |
|
||||
| Phone screenshots | JPEG or 24-bit PNG without alpha, shorter side ≥ 320 px, longer side ≤ 3840 px, **longer side ≤ 2× shorter side** | `store-assets/screenshots/0{1,2,3}-*.png` — 1080×2160, RGB | `tools/make-store-screenshots.sh`, from raw `adb exec-out screencap` captures in `store-assets/raw-screenshots/` |
|
||||
|
||||
Two details worth keeping in mind:
|
||||
|
||||
- The icon is **full bleed**: the adaptive-icon background covers all 108 dp, so every pixel
|
||||
of the PNG ends up opaque and Play's mask can crop to any shape without showing a gap.
|
||||
- A phone screenshot at the test device's native 1080×2400 is **2.22:1 and therefore
|
||||
rejected** — Play's limit is 2:1. The normaliser crops the status bar (93 px) and the
|
||||
navigation bar (130 px) and clamps to 1080×2160, which removes system chrome only, never
|
||||
app content. Re-measure both insets if you shoot on another device and pass
|
||||
`STATUS_BAR_PX` / `NAV_BAR_PX`.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Captured on the test device (1080×2400, Android 12 / MIUI), cropped to 1080×2160:
|
||||
|
||||
| File | Screen |
|
||||
|---|---|
|
||||
| `01-empty.png` | Intro / empty state before the first scan |
|
||||
| `02-scanning.png` | Scanning progress — *“Looking for visually similar photos…”* 5341 / 5363 |
|
||||
| `03-results.png` | Results — 15 duplicate groups, 39.8 MB reclaimable, `BEST` badge, selection bar |
|
||||
|
||||
⚠️ **Two caveats before these go live.**
|
||||
|
||||
1. All three still show the *“Photo access needed”* banner. The test device (Xiaomi M2101K7AG
|
||||
/ MIUI) refuses `pm grant`, `install -g` and synthetic taps, so the runtime permission was
|
||||
never actually granted there — the scan was driven with a debug-only
|
||||
`MANAGE_EXTERNAL_STORAGE` app-op, which the app's permission check does not honour (see
|
||||
[EDGE_CASE_TESTING](EDGE_CASE_TESTING.md)). **Re-shoot on a clean install after tapping
|
||||
“Grant access”**: that also closes the one remaining device-only verification gap.
|
||||
2. The results screenshot shows real photos from the test library. They are the developer's
|
||||
own, so they can be published, but check that nothing personal is recognisable first.
|
||||
|
||||
Screens still worth capturing once a device grants access through the real flow — each needs
|
||||
one tap, which is exactly what the MIUI test device cannot be driven to do:
|
||||
|
||||
- the **Options** sheet with the three sensitivity presets,
|
||||
- the staged state (*“All duplicates are ready to go”*) after selecting duplicates,
|
||||
- Android's **confirmation dialog** for moving photos to the trash.
|
||||
|
||||
## Store listing copy
|
||||
|
||||
### App name (max 30 characters)
|
||||
|
||||
Foto Dup Finder
|
||||
|
||||
### Short description (max 80 characters)
|
||||
|
||||
Find and remove duplicate photos. Free, ad-free, works offline.
|
||||
|
||||
Alternative, if the no-upload promise should lead:
|
||||
|
||||
Find and delete duplicate photos. No ads, no uploads, no account.
|
||||
|
||||
### Full description (max 4000 characters)
|
||||
|
||||
Find and remove duplicate photos, then get the space back. Foto Dup Finder does one
|
||||
job and does it without ads, without an account and without ever uploading anything.
|
||||
|
||||
WHY IT IS DIFFERENT
|
||||
• No ads — no banners, no interstitials, no upsells.
|
||||
• No internet permission at all, so the app has no way to upload your photos.
|
||||
• No account, no sign-in, no tracking, no analytics.
|
||||
|
||||
WHAT IT DOES
|
||||
• Finds exact copies: files that are byte-for-byte identical, even when their names
|
||||
differ.
|
||||
• Finds near-duplicates: the same picture saved twice, or two shots of the same
|
||||
scene. A perceptual hash compares how the images actually look rather than only
|
||||
their bytes, so a re-compressed or slightly re-cropped copy is still caught.
|
||||
• Suggests what to keep. The best copy of each group is marked BEST, and
|
||||
"Select all but best" marks the rest in a single tap.
|
||||
• Lets you choose how far to go: Exact copies only, Balanced (recommended) or
|
||||
Aggressive.
|
||||
• Deletes safely. Every removal goes through Android's own confirmation dialog, and
|
||||
you choose between moving photos to the trash (recoverable) and deleting them
|
||||
permanently. Nothing is removed without your confirmation.
|
||||
• Copes with a messy library. RAW files and unreadable or corrupt images are skipped
|
||||
and reported instead of stopping the scan.
|
||||
• Much faster the second time. File hashes are remembered on the device, so
|
||||
re-scanning an unchanged library takes a fraction of the first run.
|
||||
|
||||
HOW IT WORKS
|
||||
1. Grant photo access — everything runs on your device.
|
||||
2. Tap Start scan. A library of several thousand photos is done in seconds.
|
||||
3. Review the groups, keep the copies you want and select the rest.
|
||||
4. Tap "Remove all selected" and confirm once for the whole batch.
|
||||
|
||||
YOUR PHOTOS STAY YOURS
|
||||
Foto Dup Finder declares no internet permission, ships no advertising or analytics
|
||||
SDKs, and stores nothing besides a local index of file hashes so the next scan can be
|
||||
quick. No photo ever leaves your phone, and uninstalling the app removes the index
|
||||
with it.
|
||||
|
||||
GOOD TO KNOW
|
||||
• Requires Android 8.0 or newer.
|
||||
• Photos moved to the trash can be restored from your gallery app until the trash is
|
||||
emptied (Android 11 and newer).
|
||||
• Works fully offline, in flight mode, always.
|
||||
|
||||
Every claim above is checkable against this repository: no `INTERNET` permission in the
|
||||
merged manifest, no ad or analytics dependency in `gradle/libs.versions.toml`, `minSdk 26`
|
||||
in `app/build.gradle.kts`, and the second-run speed-up is the persistent hash index in
|
||||
`data/PhotoIndexStore.kt`.
|
||||
|
||||
### Category and tags
|
||||
|
||||
- **Category:** Tools (alternative: Photography — *Tools* matches the one-job utility framing
|
||||
better, and Photography is more crowded).
|
||||
- **Tags:** duplicate photos, photo cleanup, storage.
|
||||
- **Contact details:** the same e-mail as in [PRIVACY.md](../PRIVACY.md) — replace the
|
||||
`support@example.com` placeholder there first.
|
||||
Reference in New Issue
Block a user