Re-shoot the phone screenshots, and fix what taking them exposed
The phone set was two days old and predated M16-M20: nothing in it showed the copy-to picker, the trash's window or the compress dialog. It is now eight shots (Play's maximum) taken from the signed release build over the demo library: home, folder list, photo grid, storage usage, sort sheet, Copy to..., the trash with its 24-hour line and Empty trash, and the compress dialog with its switch. The shots have to stay inside the demo library, so two things were deliberately not captured: the home screen (its library counts are the real phone's - the old AVD-shot file is kept) and the picker one level down (it would name real folders). The picker is shot at the volume list instead. Taking the picker shot exposed a real bug: it listed '.plainfiles-trash' among the destinations, because unlike every other listing it did not filter hidden entries. Hidden folders are now offered only when the listing shows hidden files, with a test for both halves. The tablet sets are unchanged. The tablet AVDs cannot boot on this machine (no KVM) and the phone-resized path needs more navigation work than this session had left; the checklist says so and records what to regenerate and where.
@@ -622,7 +622,10 @@ class BrowserViewModel(
|
|||||||
} else {
|
} else {
|
||||||
runCatching { source.list(path) }.getOrDefault(emptyList())
|
runCatching { source.list(path) }.getOrDefault(emptyList())
|
||||||
}
|
}
|
||||||
entries.filter { it.isDirectory }
|
// Folders only, and hidden ones only when the listing shows hidden files: the picker is a
|
||||||
|
// listing, and a `.plainfiles-trash` or a `.thumbnails` folder in a copy dialog is an
|
||||||
|
// invitation to put a file somewhere it will never be seen again.
|
||||||
|
entries.filter { it.isDirectory && (_state.value.showHidden || !it.isHidden) }
|
||||||
.sortedWith(SortSpec().comparator)
|
.sortedWith(SortSpec().comparator)
|
||||||
.map { PickerFolder(path = it.path) }
|
.map { PickerFolder(path = it.path) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,6 +311,26 @@ class BrowserViewModelTest {
|
|||||||
assertEquals(R.string.browser_internal_storage, crumb.labelRes)
|
assertEquals(R.string.browser_internal_storage, crumb.labelRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `the picker leaves hidden folders out until they are asked for`() {
|
||||||
|
storage.addDirectory(root.child(".hidden-backup"))
|
||||||
|
val model = viewModel()
|
||||||
|
model.openDirectory(downloads)
|
||||||
|
model.toggleSelection(downloads.child("a.txt"))
|
||||||
|
|
||||||
|
model.startTransferTo(cut = false)
|
||||||
|
model.pickerOpen(root)
|
||||||
|
|
||||||
|
// A copy dialog is a listing: the trash folder and a `.thumbnails` folder are not destinations.
|
||||||
|
val offered = model.state.value.transferPicker!!.folders.map { it.path.name }
|
||||||
|
assertFalse(".hidden-backup" in offered)
|
||||||
|
|
||||||
|
// Showing hidden files is a statement about what the user wants to see, so the picker follows it.
|
||||||
|
model.toggleShowHidden()
|
||||||
|
model.pickerOpen(root)
|
||||||
|
assertTrue(".hidden-backup" in model.state.value.transferPicker!!.folders.map { it.path.name })
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `the picker walks up to the volumes and back down`() {
|
fun `the picker walks up to the volumes and back down`() {
|
||||||
val model = viewModel()
|
val model = viewModel()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Generated from the source by `tools/make-code-map.py` - do not edit by hand. Regenerate it after a change (`python3 tools/make-code-map.py`); `--check` exits non-zero when this file is stale.
|
Generated from the source by `tools/make-code-map.py` - do not edit by hand. Regenerate it after a change (`python3 tools/make-code-map.py`); `--check` exits non-zero when this file is stale.
|
||||||
|
|
||||||
80 source files, 13899 lines - 37 test files, 430 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification.
|
80 source files, 13902 lines - 37 test files, 431 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification.
|
||||||
|
|
||||||
Start at `AGENTS.md`. What is done, and why: `docs/ROADMAP.md` and `CHANGELOG.md`. What was verified by hand rather than by a test: `docs/EDGE_CASE_TESTING.md`.
|
Start at `AGENTS.md`. What is done, and why: `docs/ROADMAP.md` and `CHANGELOG.md`. What was verified by hand rather than by a test: `docs/EDGE_CASE_TESTING.md`.
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ The browser screen: view model, state, list and grid, dialogs, selection bar
|
|||||||
| `BrowserScreen.kt` | 661 | The browser, and everything that can be on top of it. BrowserScreen(), messageText() | - |
|
| `BrowserScreen.kt` | 661 | The browser, and everything that can be on top of it. BrowserScreen(), messageText() | - |
|
||||||
| `BrowserTopBar.kt` | 238 | The browser's app bar: title, up, the favourite star, and the search / sort / overflow actions. BrowserTopBar(), SEARCH_FIELD_WIDTH | `BrowserTopBarTest` (4) |
|
| `BrowserTopBar.kt` | 238 | The browser's app bar: title, up, the favourite star, and the search / sort / overflow actions. BrowserTopBar(), SEARCH_FIELD_WIDTH | `BrowserTopBarTest` (4) |
|
||||||
| `BrowserUiState.kt` | 391 | One immutable description of everything the browser shows. ViewMode, SearchScope, Clipboard, CompressRequest, PickerFolder, TransferPicker, MessageAction, UiMessage, ConflictPrompt, RootItem, Bookmark, DetailFocus, Undo, UndoMove, Overlay, BrowserUiState | - |
|
| `BrowserUiState.kt` | 391 | One immutable description of everything the browser shows. ViewMode, SearchScope, Clipboard, CompressRequest, PickerFolder, TransferPicker, MessageAction, UiMessage, ConflictPrompt, RootItem, Bookmark, DetailFocus, Undo, UndoMove, Overlay, BrowserUiState | - |
|
||||||
| `BrowserViewModel.kt` | 2598 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (116) |
|
| `BrowserViewModel.kt` | 2601 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (117) |
|
||||||
| `DetailsSheet.kt` | 109 | Name, location, size, type and modified date for one entry. DetailsSheet(), DetailLines(), DetailLine() | - |
|
| `DetailsSheet.kt` | 109 | Name, location, size, type and modified date for one entry. DetailsSheet(), DetailLines(), DetailLine() | - |
|
||||||
| `EntryIntents.kt` | 58 | Wraps an entry's path in a content:// URI another app may read. contentUriFor(), viewIntent(), shareIntent() | - |
|
| `EntryIntents.kt` | 58 | Wraps an entry's path in a content:// URI another app may read. contentUriFor(), viewIntent(), shareIntent() | - |
|
||||||
| `IncomingBar.kt` | 60 | The bar shown while another app is waiting on this one. IncomingBar() | - |
|
| `IncomingBar.kt` | 60 | The bar shown while another app is waiting on this one. IncomingBar() | - |
|
||||||
@@ -595,7 +595,7 @@ Every test name in the suite, by file: the fastest way to see whether something
|
|||||||
- a library or the root list has nothing to star
|
- a library or the root list has nothing to star
|
||||||
- the star gives way to the search field
|
- the star gives way to the search field
|
||||||
|
|
||||||
### `ui/browser/BrowserViewModelTest.kt` (116)
|
### `ui/browser/BrowserViewModelTest.kt` (117)
|
||||||
|
|
||||||
- starts on the root list
|
- starts on the root list
|
||||||
- opening a folder lists it with directories first and natural order
|
- opening a folder lists it with directories first and natural order
|
||||||
@@ -607,6 +607,7 @@ Every test name in the suite, by file: the fastest way to see whether something
|
|||||||
- move to a chosen folder takes the rows with it
|
- move to a chosen folder takes the rows with it
|
||||||
- the picker offers folders and nothing else
|
- the picker offers folders and nothing else
|
||||||
- the picker names a volume the way the browser does
|
- the picker names a volume the way the browser does
|
||||||
|
- the picker leaves hidden folders out until they are asked for
|
||||||
- the picker walks up to the volumes and back down
|
- the picker walks up to the volumes and back down
|
||||||
- a conflicting destination asks before overwriting, naming that folder
|
- a conflicting destination asks before overwriting, naming that folder
|
||||||
- cancelling the picker copies nothing
|
- cancelling the picker copies nothing
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ Full copy and the generated graphics land in **M10** (`docs/STORE_LISTING.md`).
|
|||||||
fields needed: app name, short description, full description, app icon 512×512, feature
|
fields needed: app name, short description, full description, app icon 512×512, feature
|
||||||
graphic 1024×500, and the screenshots.
|
graphic 1024×500, and the screenshots.
|
||||||
|
|
||||||
- [ ] Upload the screenshots from `store-assets/screenshots/`: **phone** 1080×2160 (5),
|
- [ ] Upload the screenshots from `store-assets/screenshots/`: **phone** 1080×2160 (8),
|
||||||
**7-inch tablet** 1200×1920 (4), **10-inch tablet** 2560×1600 (4). Play wants 4+ per
|
**7-inch tablet** 1200×1920 (4), **10-inch tablet** 2560×1600 (4). Play wants 4+ per
|
||||||
large-screen form factor. All three sets clear its rules — 24-bit PNG with no alpha,
|
large-screen form factor. All three sets clear its rules — 24-bit PNG with no alpha,
|
||||||
shorter side ≥ 320, longer side ≤ 3840, and longer side ≤ 2× the shorter side.
|
shorter side ≥ 320, longer side ≤ 3840, and longer side ≤ 2× the shorter side.
|
||||||
@@ -205,11 +205,16 @@ graphic 1024×500, and the screenshots.
|
|||||||
whose app area is blank.
|
whose app area is blank.
|
||||||
- [ ] Expect the 7-inch set to be single-pane and the 10-inch set two-pane. That is the
|
- [ ] Expect the 7-inch set to be single-pane and the 10-inch set two-pane. That is the
|
||||||
adaptive layout working, not an inconsistency — see the CHANGELOG.
|
adaptive layout working, not an inconsistency — see the CHANGELOG.
|
||||||
- [ ] The committed sets show browsing, a folder list, an image preview and storage usage.
|
- [x] The phone set was re-shot on 2026-10-18 and now shows **browsing, a folder list, the photo
|
||||||
**None of them show search, undo, the trash or receiving a share** — four features the
|
grid, storage usage, the sort sheet, the Copy to… picker, the trash (with its 24-hour window and
|
||||||
app now has. The existing shots stay accurate (no layout changed), so this is an
|
Empty trash) and the compress dialog** — the last three are what M16-M20 added. **Still not
|
||||||
opportunity rather than a defect: two or three more captures would let the listing
|
shown**: search, undo, and receiving a share. The picker shot is taken at the volume list on
|
||||||
advertise them. See `tools/make-store-screenshots.sh`.
|
purpose: one level down it would publish the real phone's folder names, which the demo-library
|
||||||
|
rule above forbids.
|
||||||
|
- [ ] The tablet sets were **not** re-shot: the TV/tablet AVDs cannot boot on this machine (no KVM),
|
||||||
|
and the phone-resized path needs more navigation work. They stay accurate for the screens they
|
||||||
|
show (roots, folder list, image preview, storage usage) and should be regenerated on a machine
|
||||||
|
with KVM before the listing goes live.
|
||||||
|
|
||||||
## 8. Release tracks and pricing
|
## 8. Release tracks and pricing
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ Play's slots, and what each needs:
|
|||||||
| 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 |
|
| 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 |
|
| 10-inch tablet | 4+ recommended | `screenshots/tablet-10in/` — 2560×1600 | Landscape profile (matches the `tablet10` AVD); Play accepts either orientation |
|
||||||
|
|
||||||
|
**What the phone set shows** (eight slots, the maximum): the home screen, a folder listing, the photo
|
||||||
|
grid, storage usage, the sort sheet, the **Copy to… picker**, the **trash** with its 24-hour window,
|
||||||
|
and the **compress dialog**. The last three are M16-M20's work; the picker is shot at the volume list
|
||||||
|
so it names no folder that exists on the real device.
|
||||||
|
|
||||||
### The library the shots are taken over
|
### The library the shots are taken over
|
||||||
|
|
||||||
**Never shoot over a real device's storage.** Published screenshots are public, and a file
|
**Never shoot over a real device's storage.** Published screenshots are public, and a file
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 161 KiB |