Skip to content

Photograph six screens of the app, and frame them for the store - #598

Open
andiwand wants to merge 2 commits into
mainfrom
store-screenshots
Open

Photograph six screens of the app, and frame them for the store#598
andiwand wants to merge 2 commits into
mainfrom
store-screenshots

Conversation

@andiwand

@andiwand andiwand commented Aug 18, 2026

Copy link
Copy Markdown
Member

The store copy is written down in this repository; the screenshots were not. A picture of
the app is worth what the build it came off is worth, so the release run takes its own -
six screens on a phone and a tablet, in the fifteen locales the listing is written in, 180
pictures a release - frames them and hands them to supply. Nothing is committed, and
.gitignore says so.

OpenDocument.ios landed the same arrangement in opendocument-app/OpenDocument.ios#164, and
the python here is deliberately close enough to lift out into a shared repository later.

What it is

  • ScreenshotTests is the whole of it. An instrumented test runs in the app's own
    process, so laying the samples out, filling the recent list and switching the app's
    language are things a test does directly - where iOS has to carry a ScreenshotMode
    inside the app. There is no new line in MainActivity and nothing test shaped in an apk
    that ships. Everything past that goes through the app the way a user does: the recent
    list is documents that were really opened, edit mode is the button, the find bar is typed
    into.
  • It skips itself unless a run names a device, so connectedCheck does not photograph a
    store listing nobody asked for, and refuses anything below API 35, where the app does
    not tell the system bars to follow a light theme and every picture gets a white clock on a
    white bar.
  • scripts/make-screenshot-documents.py writes the documents in the pictures, in every
    language, reproducibly. frame-screenshots.py draws the frame - a Pixel, from its
    published dimensions, nothing downloaded - onto a canvas of its own, since Play refuses a
    picture more than twice as long as it is wide and a Pixel 9 Pro XL is 2.23:1 before
    anything is drawn around it. store_screenshots.py says what a full set is and stages
    it.
  • The release grows two jobs beside the build, one emulator per device, and the listing
    upload moves out of the bundle upload
    : a bundle cannot go up twice, while the listing
    stays editable for as long as the release sits on the internal track. A screenshot run
    that wedges an emulator costs the release its pictures, not its binary.
  • The four phone screenshots under fastlane/metadata predate the 4.14 redesign and were
    never uploaded. They are gone; the release takes its own now.

Taking them by hand

python3 -m pip install Pillow
bundle exec fastlane android screenshots                       # every locale, phone
ODR_SCREENSHOT_DEVICE=tablet bundle exec fastlane android screenshots
ODR_SCREENSHOT_LANGUAGES=en-US,de-DE bundle exec fastlane android screenshots

Still to check

The release workflow's new jobs have never run - draft until a full set has been through
CI. The English set was re-taken locally at this head, phone and tablet, on API 36
emulators, and framed: twelve pictures, nothing missing.

🤖 Generated with Claude Code

andiwand and others added 2 commits August 17, 2026 23:28
The store copy beside these is written, so it lives in git and the release
uploads what is committed. A screenshot is not written, it is taken: it is
worth what the build it came off is worth. So these are taken during the
release run, from the build going out, framed there, and handed to supply from
there. Nothing is committed.

Six pictures per device - the recently opened list, a text document with a
search running, a spreadsheet, an edit under way, a pdf and a Word file - on a
Pixel Pro and a Pixel Tablet, in the fifteen locales the listing is written in.
That is 180 pictures a release.

OpenDocument.ios landed the same arrangement in #164, and the python here is
deliberately close enough to lift out into a shared repository later. What
differs is the half that had to be adjusted rather than copied.

ScreenshotTests is the whole back door. An instrumented test runs in the app's
own process, so laying the samples out, filling the recent list and switching
the app's language are all things a test can do directly - where iOS has to
carry a ScreenshotMode inside the app because a UI test there has no such
reach. There is no new line in MainActivity and nothing test shaped in an apk
that ships. Everything past that goes through the app the way a user does: the
recent list is documents that were really opened, edit mode is the button, and
the find bar is typed into.

It writes into gradle's additionalTestOutputDir, which gradle copies back
before it uninstalls the apks. getExternalFilesDir is the obvious answer and
the wrong one - an app's own storage goes with it when it is uninstalled, so
the pictures were written, the test passed, and there was nothing left to
fetch.

The frame is drawn rather than downloaded, as on the other side, but onto a
canvas of its own: play refuses a picture more than twice as long as it is
wide, and a Pixel 9 Pro XL is 2.23:1 before anything is drawn around it. The
device in it is an Android one - a punch hole rather than a pill, keys on the
right edge, tighter display corners - and it fits whole, because the buttons
this app puts in the bottom right corner are the ones a reader taps.

The documents in them are written here too, in every language, and so are the
headlines. Six languages more than the App Store side needs, since the play
listing is in fifteen locales and every one of them has an app translation, so
none borrows the English.

The bundle upload and the listing upload are now separate jobs, as they are on
the other side: a bundle cannot go up twice, while the listing stays editable
for as long as the release sits on the internal track. A screenshot run that
wedges an emulator costs the release its pictures, not its binary.

Three things the pictures themselves forced, and each is in a comment where it
bit: the search screenshot searches the report and not the invoice, which is
the document the word was counted out of; the run turns the device upright by
trying rather than by telling, because rotation 0 lays a tablet down; and it
refuses anything below Android 15, where the app does not tell the system bars
to follow a light theme and every picture gets a white clock on a white bar.

The four phone screenshots under fastlane/metadata predate the 4.14 redesign
and were never uploaded. They are gone: the release takes its own now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CocwstWzFw7M5rhj83ncUc
The frame was ported from the App Store side and still described Apple's
hardware. Three things were doing the work, and the first is most of it:

The corner. Apple's is a continuous curve - the curvature eases into the
straight edge instead of starting at full bend - and it takes a superellipse
exponent around 5. A Pixel's is very close to a circular arc, which is 2. At
this size that is not a subtlety, and it was the single reason the drawing read
as an iPhone with the logo filed off. The exponent is a layout value now, at
2.2: a touch of easing, because an exact circle reads as a render.

The metal. A Pixel Pro's frame is polished aluminium, near enough a mirror,
which is a different thing from the turned titanium the ramp was read off.
Brushed metal climbs to one broad highlight two thirds of the way in; a
polished one throws a narrow specular at the outer edge, drops away hard, and
picks up a weaker sheen where the flat turns down to the glass. It is also
warm rather than neutral, and reading the two side by side that tint is most of
what separates them at a glance.

The proportions. The black mask was 60% of the border and the bright edge a
sliver, which is a phone from 2018. On a Pixel it is the other way round - less
than half black, the rest frame - and the tablet is the other way again, mostly
mask with a thin bright edge outside it.

And the composition, which was the other half of the report: 18 pixels of the
body past the foot of the canvas against 79 down the right hand side. Off the
edge is a decision and a hair short of it is a mistake, so the device is now
sized to leave a foot rather than by a fraction that happened to nearly fit -
which also means a device whose screen is a little taller or shorter than the
one the number was picked for still stands on the ground.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CocwstWzFw7M5rhj83ncUc
@andiwand
andiwand marked this pull request as ready for review August 18, 2026 07:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90b0ba858b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


for (rotation in 0..3) {
shell("settings put system user_rotation $rotation")
SystemClock.sleep(ROTATE_MS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for each rotation to settle before advancing

On the API 36 Pixel Tablet runner, a display rotation can take longer than the fixed 2.5-second ROTATE_MS. When that happens, the screenshot still has the previous landscape dimensions, so the loop immediately requests another rotation and can exhaust all four settings without ever observing the valid portrait state. This intermittently fails the screenshots matrix and consequently blocks screenshot-set and the listing upload; poll the screenshot dimensions with a longer per-rotation timeout before advancing.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant