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.
This commit is contained in:
@@ -134,9 +134,34 @@ through the prompt, because the prompt was already covered in the M7 session.
|
||||
| The duplicate scan | **Correct.** Tapping *Find duplicates* in the overflow ran the scan and produced real groups: `DOC-20251121-WA0001.zip` and `Schwarzhofen.zip` at 10.5 MB each (identical content in `Android/media/…` and `Download/`), a PDF duplicated at 5.3 MB, another PDF present **three** times at 761.3 KB. Group headers read "2 copies · 10.5 MB" above "10.5 MB could be freed", each row carried its **folder path**, and the groups were ordered by reclaimable space |
|
||||
| Ticking a copy | **Correct.** The row highlighted and the bar action appeared as **"Delete 1 copy"** — the plural resource agrees with the count |
|
||||
| Closing the scan | **Correct.** `X` returned to the browser with nothing changed |
|
||||
| Video thumbnails | **Correct.** Two clips made for the purpose (`ffmpeg`: a testsrc2 pattern, then SMPTE bars) were pushed into the demo folder and both drew **their own pattern** as the row's thumbnail — a frame from inside the file, not a poster and not the icon — beside a real 4.3 MB video from the device's own storage. The library's three zero-filled stand-in `.mp4`s cannot be decoded and **fell back to the film icon**, which exercised the failure path with material that happened to be lying around |
|
||||
| The same rows on the **release** APK (R8) | **Correct, after a fix — and this is the one that mattered.** `coil-video` registers its decoder through a `META-INF/services` entry, and the release APK contained neither that file nor the class it named: frames would have decoded in debug and quietly fallen back to icons for anyone who installed the app. Registering the factory explicitly in `FileManagerApplication` fixed it, and the release build was installed and driven to the Videos library, where all four rows behaved as the debug ones had |
|
||||
| The search field opened from a library | **Wrong, and fixed.** The hint read *Search this folder* and a folder toggle sat beside it, while the search in fact ran everywhere — the view model has always answered from the index when there is no folder. See the findings below |
|
||||
| Crashes | **None.** `logcat -b crash` contained no entry for the package across the whole session |
|
||||
|
||||
### Two findings worth keeping
|
||||
### Four findings worth keeping
|
||||
|
||||
- **A ServiceLoader entry is not a contract, and R8 is the difference between a debug build and the
|
||||
one people install.** `coil-video` registers its decoder with Coil through a `META-INF/services`
|
||||
file. In the debug APK both that file and the class it names are present, and video thumbnails
|
||||
worked. In the **release** APK neither was: R8 removes a class that is mentioned only from a
|
||||
resource, because by its reckoning nothing uses it, and the resource goes with it. The feature was
|
||||
therefore working in every build that had been looked at and would have failed silently in the one
|
||||
that ships — the worst possible shape for a bug, 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. Registering the
|
||||
decoder explicitly against an `ImageLoader` turns it into a code reference, and the release APK was
|
||||
then installed and driven to confirm it. The lesson generalises: **audit the release build, not the
|
||||
debug one**, and never trust a registration that happens by naming a class in a file.
|
||||
|
||||
- **The search field lied about its own scope inside a library, and only a run could show it.**
|
||||
Opening the field from a library left the scope on "this folder": the hint said *Search this
|
||||
folder* and offered a folder/public toggle, while the search actually answered from the index over
|
||||
every volume. The behaviour was right and the *screen* was wrong — the same shape of bug the
|
||||
volume-list fix had, and the same root cause: the switch-on-open asked whether this was the root
|
||||
list instead of whether a folder was open, and a library is not the root list but has no folder
|
||||
either. The toggle is now shown only when there is a folder to choose about, because its two
|
||||
settings did the same thing otherwise. Nothing in the suite could have caught this: it was a
|
||||
mismatch between two correct pieces, which is what a device pass is for.
|
||||
|
||||
- **The library views are device-wide, so they cannot be photographed on a real phone.** The
|
||||
`Images` list showed the owner's own files — names and thumbnails included — because a library
|
||||
|
||||
Reference in New Issue
Block a user