Skip to content

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

Merged
andiwand merged 5 commits into
mainfrom
store-screenshots
Aug 18, 2026
Merged

Photograph six screens of the app, and frame them for the store#598
andiwand merged 5 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
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".

Comment thread app/src/androidTest/java/app/opendocument/droid/test/ScreenshotTests.kt Outdated
andiwand and others added 3 commits August 18, 2026 17:11
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
`speak()` went through `AppCompatDelegate.setApplicationLocales`, which from api 33
on only forwards to the framework's `LocaleManager` - and only once an AppCompat
activity has attached itself to the delegate. Called with nothing on screen, which
is where it has to be called, it returned having done nothing at all: every
locale's set came out with the app in English. The pictures had Portuguese file
names over an English "Recent" and "2 hours ago". It sets the locale through
`LocaleManager` now, and waits for the new configuration to reach the process
rather than launching into the one it was.

Portuguese never got that far. The documents are written as `pt-BR`, which is what
`make-screenshot-documents.py` calls the language, while both tables reading them
said `pt` - so the run would have died on `sample-text-pt.odt` eleven locales in.
That table is one table now: `store_screenshots.LOCALES` is checked against the
generator's own languages and written into the test apk's assets, and
`ScreenshotTests` reads it from there instead of holding a copy.

And the rotation, which #598's review asked about: a fixed 2.5s sleep and one
photograph, so a display that took longer moved on to the next setting and could
run out of settings without ever seeing the portrait it had asked for. The beat
stays - asked too early the screen answers with the way up it is leaving - and a
poll follows it.

Also: `dressed` is marked before the dressing rather than after, so a failure
halfway is still undone; `headline()` cannot reach its faces unbound; a mac with
neither Hiragino nor PingFang installed falls back to Arial Unicode rather than
refusing to draw a CJK headline; the unused `lay_out` and the version argument
`uploadBundle` no longer reads are gone; the release's header and the README's job
count say six; and the comments that recounted how a number was arrived at have
been cut back to what the number does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBGYtacAAsAiA19UJGvdXb
andiwand and others added 2 commits August 18, 2026 20:13
Asked for a language it has no layout for, gboard covers its own keys with a
picker - two layouts, `Skip` and `Next` - and the edit screenshot came out of a
keyboard being set up rather than of a document being edited. Japanese asks; the
other fourteen do not.

The class is what is looked for rather than the word: a key is a `FrameLayout`
carrying a description, and what the picker puts up is a real `Button` with a
word on it, so there is no list of buttons per language and the next thing gboard
decides to ask goes the same way. `uiAutomation` has to be told to answer with
the keyboard's window at all - it is not the active one - which the dressing does
once.

And `pick-avd-profile.sh` looked for `avdmanager` on the path, where a runner does
not have it: the tablet job of the first dry run died at exit 127 before an
emulator was ever started. It is looked for under the sdk now, and a runner with
neither takes the newest name unchecked rather than costing the release its
pictures - a name that does not exist is refused by the emulator action a minute
later, and says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBGYtacAAsAiA19UJGvdXb
…hed on

A page fitted to the width of a phone is about two thirds of its height, so a
document that ends after one page was photographed with a third of the picture
showing the backdrop behind it - a third of the .odt, and half of the .pdf and the
.docx. Six screens, three of them ending in grey.

The length has to come from somewhere that is already written in fifteen
languages, so it comes from the figures:

- The **report** gains the costs table under the section it is the figures for.
  No heading of its own - that would be a word to translate fifteen times - and
  it reads as a quarterly report with its numbers in it, which is what it should
  have looked like anyway.
- The **contract** gains the appendix its own clauses promise, and is set at 14pt
  rather than 11. The renderer honours neither `w:spacing` on a paragraph nor
  `w:trHeight` on a row, so the rows and the size are the only length there is to
  give it; a clause is a paragraph of its own now rather than half of one.
- The **invoice** bills for forty lines instead of twenty and breaks onto a
  second page, which the writer could not do before: it emits a page and a
  content stream each now, with the column heads written again above the rows
  that carried over. It is the only one of the three that really paginates - the
  other two are drawn as one continuous page - so it is also the only one that
  needed it.

Measured on both: every screen of both devices now reaches the foot of the picture,
where three of six ended between a fifth and a third short.

`odf_row` comes out of `sheet` so the report can set a table with the same cells,
which is also the one place the two documents have to agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBGYtacAAsAiA19UJGvdXb
@andiwand
andiwand merged commit e09810e into main Aug 18, 2026
11 of 12 checks passed
@andiwand
andiwand deleted the store-screenshots branch August 18, 2026 18:45
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