PageScribe joins the UmbraTools suite, so give it an icon in the shared
"umbra" identity: an open bronze book bound along its gutter by a glowing
cyan weave, held inside a broken reticle ring on a midnight field.
- Full adaptive icon: background + foreground + themed monochrome layer,
with the mark sized to the 66dp safe circle so circle, squircle and
rounded-square launcher masks all crop it cleanly.
- Legacy ic_launcher / ic_launcher_round bitmaps at all five densities.
- 1024 master lockup, Play Store 512 icon and the rounded-square mark in
brand/, next to the generator and design notes.
- brand/generate_icons.py rebuilds every asset deterministically (SVG via
cairosvg for the gradients and glow, Montserrat for the wordmark);
brand/README.md documents the palette, what each element means, and how
to regenerate.
The old flat vector drawable/ic_launcher.xml is replaced; the manifest now
points at @mipmap/ic_launcher with a roundIcon.
A camera you are not looking at has to say it heard the tap. A synthesised
shutter_click.wav (110 ms, two filtered noise transients for the shutter
open and close) is bundled and played through SoundPool on each accepted
shot.
The click sits behind the capture guards, so a tap the camera refuses -
still busy, or not ready - stays silent and the click always means
"taken". SoundPool loads asynchronously, so a tap that beats the sample
into memory plays a short system tone instead of nothing. The sample is
released in onDestroy.
Verified on device: with system touch sounds disabled, dumpsys audio shows
player piid:1263 (package:com.bm.pagescribe, type:android.media.SoundPool)
emitting event:started on the tap, while AudioFlinger's mixer leaves
standby at the same moment.
The printed page number is read anyway for ordering, so use it to keep
count of what has actually been captured.
PageCoverage turns the folios in pages/ into a span [low, high] with one
state per page: captured, needs re-capture (a page file that exists but is
blank - the folio was read, the body was not), or missing (no page file at
all). The shelf, book and capture screens surface it, and the book list
interleaves the gaps in folio order, so a page that was skipped or came out
blank sits exactly where the reader would look for it. A photo holding no
text at all has no folio to attribute; it stays in failed/ and is listed
separately as unreadable.
A one- or two-line half is not a text block, so PageNumberDetector's
top/bottom band test could never recognise a folio on the very page this
feature cares about - a body that failed to OCR, leaving only its page
number. Sparse halves now accept an isolated bare number sitting in the
outer margin.
98 unit tests, 0 failures (was 83). Verified end to end on the Pixel 7:
"Pages 42-51 - 7 of 10 captured - 3 to shoot", with page 47 flagged for
re-capture and 48/49 reported missing.
targetSdk 36 enforces edge-to-edge and the built-in AppCompat action bar
is no longer inset correctly: the dump showed the action bar at
[0,136..283] while android:id/content started at y=0, so the first list
row sat under the bar and taps on it never reached the row.
Replace the theme action bar with a MaterialToolbar in an AppBarLayout on
each screen (NoActionBar theme) and apply the system-bar insets explicitly
via util/EdgeToEdge: the status-bar inset pads the app bar (whose
background then paints the strip behind the bar) and the navigation-bar
inset pads the content below it. enableEdgeToEdge with a dark status-bar
style keeps the icons light against the always-dark toolbar.
Verified on a Pixel 7: content now starts at y=283, the first book row is
tappable and opens the camera, the capture screen binds CameraX and gets
its first frame, and BookActivity lays out with a working up arrow.
Add README.