Commit 726a400
committed
feat(android): add photo-permission rationale and recent-photos strip
Layers on the Photos / Camera tiles introduced in #509 with a full
permission state machine, a rationale card, and a recent-photos
thumbnail strip.
- Three media-strip states resolved at runtime by `resolveMediaStripView`
in `PhotoAccessState.kt`:
1. **Rationale card** — shown when `READ_MEDIA_IMAGES` isn't granted
and the user hasn't dismissed it. Body copy and primary-button
label switch across three sub-states (`Unasked` / `Denied` /
`PermanentlyDenied`); SharedPreferences tracks the first-prompt
flag because `shouldShowRequestPermissionRationale` alone can't
tell "never asked" from "permanently denied".
2. **Compact tiles** — once the rationale is rejected, the Photos /
Camera column flattens into a full-width 88dp row. The rejection
auto-clears when permission is later granted, so a future
revocation surfaces the rationale again.
3. **Full strip** — once granted, queries MediaStore for the 64 most
recent images and renders them as a horizontally-scrolling 2-row
thumbnail grid. LazyRow keeps off-screen thumbnails out of memory.
- Strip is gated to Android 10+ (`ContentResolver.loadThumbnail` is
API 29+); on Android 7-9 the inserter falls back to the permissionless
Photos/Camera tile row and never requests the media permission.
- Process-wide thumbnail cache keyed on (uri, sizePx) — 32 entries,
~6MB worst case. `RealThumbnail` seeds from the cache synchronously
so scroll-back and dialog reopen skip the grey-placeholder flash.
Failed-to-load URIs drop from the displayed strip and re-attempt on
the next dialog open.
- Android 14+ partial-grant treated as granted by also checking
`READ_MEDIA_VISUAL_USER_SELECTED` when `READ_MEDIA_IMAGES` is denied.
Without this, a "Select photos" choice fell through to the
rationale's "Open Settings" state for a permission the user had just
granted.
- Photo-prefs reads off the main thread via a process-wide cache
warmed from `GutenbergView`'s constructor on `Dispatchers.IO`.
Writes update the cache synchronously and queue
`SharedPreferences.edit().apply()` on the IO scope.
- Soft-input mode `STATE_HIDDEN | ADJUST_RESIZE` — `STATE_HIDDEN`
dismisses an in-flight IME on open; `ADJUST_RESIZE` lets the sheet
shrink to make room when the user taps the in-dialog search field.
- Observes the host Activity's lifecycle (not the BottomSheetDialog's
own) for `ON_RESUME`, so grants made via system Settings update the
strip on return without restart.
- `GutenbergView.resetBlockPickerPhotoPreferences(context)` exposed for
host apps that want to clear the rationale-rejection / first-prompt
flags from a settings screen — also wired into the demo's `⋮` menu
as **Manage Permissions**. Demo uses a Settings hand-off rather than
`revokeSelfPermissionOnKill` (API 33+, demo's `minSdk = 24`).
- Library declares `READ_MEDIA_IMAGES` and `READ_EXTERNAL_STORAGE`
(max SDK 32). Host apps can opt out via `tools:node="remove"`;
documented in `docs/integration.md` (Android → Manifest Permissions),
including the `xmlns:tools` namespace requirement, with the opt-out
XML inlined as a comment in `AndroidManifest.xml` for auditors
diffing the merged manifest.
- Demo's "Enable Native Inserter" toggle defaults to on so reviewers
see the new strip without flipping a setting; the standalone-editor
E2E test toggles it off so the existing web-inserter assertions
still resolve.
Touch targets on the rationale buttons meet the Material 48dp minimum
via a wrapper that keeps the visual 32dp pill while inflating the click
area; a shared `MutableInteractionSource` keeps the ripple drawing
inside the pill instead of as a square halo.
Verified on Pixel 9 Pro XL with \`./gradlew :Gutenberg:detekt
:Gutenberg:assembleDebug :Gutenberg:testDebugUnitTest\` (includes
\`PhotoAccessStateTest\`).1 parent 229de4e commit 726a400
14 files changed
Lines changed: 1163 additions & 39 deletions
File tree
- android
- Gutenberg/src
- main
- java/org/wordpress/gutenberg
- inserter
- res/values
- test/java/org/wordpress/gutenberg/inserter
- app/src
- androidTest/java/com/example/gutenbergkit
- main
- java/com/example/gutenbergkit
- docs
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
6 | 25 | | |
7 | 26 | | |
8 | 27 | | |
| |||
Lines changed: 22 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
40 | 42 | | |
41 | 43 | | |
42 | 44 | | |
| |||
219 | 221 | | |
220 | 222 | | |
221 | 223 | | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
222 | 230 | | |
223 | 231 | | |
224 | 232 | | |
| |||
1090 | 1098 | | |
1091 | 1099 | | |
1092 | 1100 | | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
1093 | 1115 | | |
1094 | 1116 | | |
1095 | 1117 | | |
| |||
0 commit comments