Files
file-manager/docs/STORE_LISTING.md
T
SoftwareApp 47e48f054e Give a video row a frame of the video, and fix what R8 was hiding
A video row showed a film-strip icon, and a phone holds more video than anything else, so a column of identical icons said nothing about which clip was which. Images and videos now take the same path - EntryIcon hands Coil the file, and for a video the request asks for a frame - which cost exactly one dependency: io.coil-kt.coil3:coil-video, at the same version as the Coil already in use, whose own POM adds nothing that Coil did not already bring (coil-core, kotlin-stdlib, core-ktx). No ExoPlayer, no media3, and no new permission: the decoder is the platform's own MediaMetadataRetriever.

Two details are decisions rather than defaults. The frame is taken one second in, because the first frame of a recording is very often black or the middle of a fade-in, and a thumbnail of a black rectangle is no more useful than the icon it replaced. And a decode that fails - an unsupported codec, or a file only pretending to be a video - settles on the icon rather than leaving an empty square, which the row remembers so it is not retried on every recomposition.

The dependency also nearly shipped a feature that did not work. coil-video registers its decoder through a META-INF/services entry, and in a debug APK that is enough: the file and the class it names are both there, and frames decoded. In the release APK, R8 removes both, because a class mentioned only from a resource looks unused to it. So the feature worked in every build that had been looked at and would have failed silently in the one users install - the worst shape a bug can have, since it needs no code change to "fix" and would have been reported as "video thumbnails don't work" against a build nobody had run. The factory is now registered explicitly against an ImageLoader in FileManagerApplication, which makes it a code reference R8 cannot drop, and the release APK was installed and driven to the Videos library to confirm the frames decode there too. That also closes the gap noted two changes ago, that no ImageLoader was registered at all; its cache sizes are left to Coil deliberately, since they are derived from the device's heap and a number written here would only be a guess.

The same run found a smaller thing that no test could have: opening the search field from inside a library left the scope on "this folder" - the hint read "Search this folder" and a folder toggle sat beside it - while the search in fact ran everywhere, because the view model has always known that a library is not a folder and answered from the index instead. The behaviour was right and the screen was wrong, which is the same shape as the volume-list bug fixed earlier and the same root cause: the switch-on-open asked whether this was the root list rather than whether a folder was open. It now asks for a path, and the scope toggle appears only when there is a folder to choose about, because its two settings did the same thing otherwise. Every other use of isRootView was then checked for the same conflation and none was found. One test was added for the search behaviour; the scope mismatch itself is not testable, being a disagreement between two correct pieces.

324 JVM tests, lint clean. Verified on the phone in both builds: two clips made with ffmpeg (a testsrc2 pattern and SMPTE bars) drew their own patterns as thumbnails, beside a real 4.3 MB video from the device's own storage, and the demo library's three zero-filled stand-in .mp4s fell back to the icon - which exercised the failure path with material that happened to be lying around. The release APK is 2 294 021 bytes and still declares no INTERNET. Not done: the store screenshots still show the home screen from before the library views, and the release build running on a device is in nobody's checklist - which is exactly why the R8 problem survived until now, and is the first thing worth adding.
2026-10-17 01:40:35 +02:00

207 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Play Store listing (M10)
Ready-to-paste copy for the *Main store listing* page, plus the graphics that go with it.
Every graphic is generated from the app's own vectors, so a rebrand is a re-run rather than
a redraw.
See [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) § 6 for where this fits in the
release.
## App name
**Plain Files**
Deliberately not "File Manager": dozens of apps are literally called that, and a distinctive
name is what makes a listing findable and rememberable. It is one string
(`app_name` in `values/strings.xml`); the `applicationId` is separate and already locked.
## Graphics
```bash
./tools/make-store-assets.sh # icon + feature graphic -> store-assets/
./tools/make-store-screenshots.sh # phone -> screenshots/phone/
FORM_FACTOR=tablet-7in ./tools/make-store-screenshots.sh
FORM_FACTOR=tablet-10in ./tools/make-store-screenshots.sh
```
## Screenshots
Play's slots, and what each needs:
| Slot | How many | Target here | Notes |
|---|---|---|---|
| Phone | **2–8** | `screenshots/phone/` — 1080×2160 | A phone at its native 1080×2400 is 2.22:1 and is **rejected**; the normaliser crops chrome and clamps to 2:1 |
| 7-inch tablet | 4+ recommended | `screenshots/tablet-7in/` — 1200×1920 | A tablet frame is already inside the 2:1 cap, so 0 px of crop |
| 10-inch tablet | 4+ recommended | `screenshots/tablet-10in/` — 2560×1600 | Landscape profile (matches the `tablet10` AVD); Play accepts either orientation |
### The library the shots are taken over
**Never shoot over a real device's storage.** Published screenshots are public, and a file
list exposes real folder names and document titles. The library is generated instead:
```bash
python3 tools/make-demo-library.py # -> demo-files/ (git-ignored, ~161 MB)
adb push demo-files /sdcard/Download/PlainFiles-Demo
```
⚠️ **The home screen and the library views need this more than a folder listing does.** A library is
not a folder — it lists files from *everywhere* — so shooting `Images` on a real phone publishes the
owner's own filenames and thumbnails, and the home screen's counts publish how many files of each
kind the device holds. Both must be shot on a device, or an AVD, whose only storage is the demo
library. This was found by running the libraries on a real phone and seeing its owner's files in
them: [EDGE_CASE_TESTING](EDGE_CASE_TESTING.md).
`tools/make-demo-library.py` builds eight distinct sample images (so the grid view and
thumbnails have something to show), a handful of documents, sized stand-ins for video and
audio so the storage-usage screen has something worth ranking, and a real ZIP so the extract
action can be exercised. Every name and every pixel is fake by construction.
One consequence worth knowing before shooting the Videos library: the video stand-ins are
**zero-filled**, so they cannot be decoded and photograph as the fallback film icon rather than as a
frame. A screenshot that shows video thumbnails needs a real clip, which is one command:
```bash
ffmpeg -f lavfi -i "testsrc2=duration=3:size=320x240:rate=15" -pix_fmt yuv420p clip.mp4 # synthetic, no camera needed
adb push clip.mp4 /sdcard/Download/PlainFiles-Demo/Video/
```
| 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**, ~5 KB | `tools/store-assets/icon.svg`, mirroring `ic_launcher_background.xml` + `ic_launcher_foreground.xml`. Background, paths and opacities are identical, so **the store icon is the launcher icon** rather than a lookalike that drifts. |
| 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 | no alpha, shorter side ≥ 320, longer side ≤ 3840, **longer side ≤ 2× shorter** | `store-assets/screenshots/phone/` (1080×2160) | `tools/make-store-screenshots.sh`, from `adb` captures in `store-assets/raw-screenshots/phone/` |
Two details worth keeping:
- **The icon is full bleed.** The background rect covers all 108 dp, so every pixel is
opaque and Play's mask can crop to any shape without showing a gap.
- **A phone at its native 1080×2400 is 2.22:1 and is therefore rejected.** The normaliser
crops the status and navigation bars and then clamps to 1080×2160 — system chrome only,
never app content. Tablets are already inside the 2:1 cap, so their crop defaults to 0.
### The feature graphic's safe area
Play crops the feature graphic to a **centre square** for some placements, so all of the copy
sits inside **x 262…762** and the motif — which does not survive — is decoration. Verified by
actually cropping the rendered PNG rather than by trusting the arithmetic:
```bash
magick store-assets/feature-graphic-1024x500.png -gravity Center -crop 500x500+0+0 +repage /tmp/square.png
```
## Short description (80 characters max)
```
Browse, organise and manage your files. Ad-free, offline, pay once.
```
## Full description (4000 characters max)
```
Plain Files is a straightforward file manager for Android — no ads, no account, no
subscription, and no network access at all. You buy it once and it is yours.
BROWSE ANYTHING
• Internal storage, SD cards and USB drives, side by side
• List or grid view, with sorting by name, size, date or type
• Breadcrumb navigation, and a search that can look in one folder or everywhere
• Hidden files on request
ANY SIZE OF SCREEN
• On a tablet or a wide window you get two panes: the folder list on the left, and what is
inside the folder you pick on the right — no jumping back and forth
• Pictures preview at full size beside the list, with their details
• The same layout on a phone, a 7-inch tablet and a 10-inch tablet
WORKS WITH YOUR OTHER APPS
• Share a file to Plain Files from anywhere, then pick the folder to drop it in
• Choose Plain Files when another app asks you for a file
• Search everywhere at once: an index means answers arrive right away instead of after a
long scan — and it always says how current it is
• Delete, rename or move by mistake? Undo it
MANAGE YOUR FILES
• Copy, move, rename and delete, one item or many at once
• Create folders and edit text files in the built-in editor
• See the details of anything: size, type, location, modified date
• Open a file in any app, or share it
FIND OUT WHERE THE SPACE WENT
• A storage-usage view that measures every folder and ranks it largest first
• Spot the folder that is actually eating your storage, then act on it
ARCHIVES
• Create ZIP archives, and extract ZIP, TAR, TAR.GZ, TAR.BZ2, TAR.XZ, GZ, BZ2, XZ and 7z
• Look inside an archive before you extract anything — text, images, documents, audio and
video included, read straight out of the file
• RAR is not supported — it needs native code this app does not ship, and it says so rather
than failing with a strange error
PRIVATE BY DEFAULT
• No INTERNET permission. The app cannot send your data anywhere, because it has no way to.
• No analytics, no advertising SDKs, no third-party trackers
• No account, no sign-in, nothing to remember
• Your files stay on your device, always
SOLD ONCE
• One purchase, every feature, no ads and nothing to buy inside the app
• No subscription
PERMISSIONS, EXPLAINED
Plain Files asks for "All files access" because that is what browsing and managing files
anywhere on a device requires — the app's core purpose. Google Play only grants this
permission to app types that genuinely need it, and a file manager is one of them. If you
would rather not grant it, the app still works: it can browse a folder you pick yourself
through Android's own folder picker, and it remembers that choice.
Read the privacy policy: https://example.invalid/plain-files/privacy
```
> Replace the privacy-policy URL with the real public URL before publishing — a repository
> file is not acceptable to Play. [PRIVACY.md](../PRIVACY.md) is the source text.
### Why the permission sentence matters
Play requires that an app's **core functionality be prominently documented and promoted in
the app's description** when it declares `MANAGE_EXTERNAL_STORAGE`. The "PERMISSIONS,
EXPLAINED" section is not marketing copy — it is the evidence the Permissions Declaration
Form asks for.
## Ad headlines (App campaign, 30 characters each)
Google App campaigns take **up to five headlines**, and the system mixes and matches them, so
each one has to stand on its own rather than continue the one before it. Counts include spaces
and are measured, not estimated:
| Headline | Chars | Angle |
|---|---|---|
| Ad-free file manager | 20 | The whole pitch, in three words |
| Pay once, keep it forever | 25 | One-time price, no subscription |
| Works offline. No network. | 26 | The privacy claim the APK actually backs |
| SD cards and USB drives | 23 | Reach beyond internal storage |
| See where your storage went | 27 | The storage-usage view |
Spares, to swap in when an asset group wants a different emphasis: `Two panes on your tablet`
(24), `Undo a delete you regret` (24), `Built for phones and tablets` (28).
Three rules these are written to:
- **Never "free".** This is a paid app, so a headline implying otherwise is misleading as well
as a policy problem. The launch price is the only discount, and it belongs in the price field,
not in a headline.
- **No superlatives.** "Best", "top" and "#1" are unverifiable claims, and they are not how this
app sells anyway — the differentiator is what it *does not* contain.
- **Sentence case, no shouting.** No all-caps text, no emoji, no stacked punctuation; Google's
editorial policy refuses all three, and it matches the voice of the listing copy above.
The app name is taken from the store listing (*App name* above) and is not repeated in a
headline.
## Still to do before publishing
- **Screenshots.** `tools/make-store-screenshots.sh` is written and the raw-screenshot
directories are in place, but no captures have been taken yet, so
`store-assets/screenshots/` is empty. Capture against a **synthetic** library — never a
real one, since published screenshots would expose real file names.
- **Tablet screenshots.** Play has a 7-inch and a 10-inch slot, each wanting 4+.