mkit-cli 0.3.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/CHANGELOG.md +38 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/PKG-INFO +29 -1
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/README.md +28 -0
- mkit_cli-0.5.0/docs/about-screen.md +228 -0
- mkit_cli-0.5.0/docs/app-ui.md +123 -0
- mkit_cli-0.5.0/docs/aso.md +199 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/docs/commands.md +31 -4
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/docs/config.md +62 -2
- mkit_cli-0.5.0/docs/in-app-review.md +351 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/docs/in-app-update.md +2 -3
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/docs/news-topics.md +1 -13
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/docs/store-art.md +4 -4
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/docs/store.md +1 -2
- mkit_cli-0.5.0/docs/support-replies.md +77 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/pyproject.toml +12 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/__init__.py +1 -1
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cli.py +7 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/__init__.py +1 -1
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/aso.py +2 -2
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/dev.py +11 -1
- mkit_cli-0.5.0/src/mkit/commands/guide.py +48 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/init.py +11 -7
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/docgen.py +1 -1
- mkit_cli-0.5.0/src/mkit/guides.py +202 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/registry.py +8 -2
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_core.py +27 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_docs.py +20 -0
- mkit_cli-0.5.0/tests/test_guides.py +142 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/.gitattributes +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/.github/workflows/publish.yml +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/.github/workflows/tests.yml +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/.gitignore +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/AGENTS.md +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/CONTRIBUTING.md +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/LICENSE +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/SECURITY.md +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/scripts/gen_docs.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/scripts/release.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/art.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/aso.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/__init__.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/auth.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/fbproject.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/firebase.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/insight.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/money.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/play.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/cloud/store.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/art.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/doctor.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/firebase_cmds.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/insight.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/meta.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/money.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/release.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/selfcmd.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/commands/store.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/config.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/data/art/art.toml +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/data/art/en.json +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/data/datasafety-questions.csv +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/datasafety.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/device.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/errors.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/git.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/hooks.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/kinds.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/notify.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/output.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/proc.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/safety.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/secrets.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/src/mkit/toolchain.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/conftest.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/fakeplay.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_art.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_aso.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_datasafety.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_device_logs.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_firebase_apps.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_network_errors.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_products.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_release.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_store.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/tests/test_studio.py +0 -0
- {mkit_cli-0.3.1 → mkit_cli-0.5.0}/uv.lock +0 -0
|
@@ -7,6 +7,44 @@ it rather than doing the wrong thing.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.5.0 — 2026-10-23
|
|
11
|
+
|
|
12
|
+
**An app can keep its own version of any guide.** A file named like the guide
|
|
13
|
+
in the app's repo — `docs/guides/about-screen.md`, `docs/guides/app-ui.md`, or
|
|
14
|
+
under `[guide] app_dir` — is added after the guide and the studio's own rules,
|
|
15
|
+
under *This app's own rules*, and wins over both. `[guide] replace = ["ui"]` in
|
|
16
|
+
the app's `mkit.toml` makes that file the whole guide. Placeholders work in it,
|
|
17
|
+
`{{app.name}}` included, and `mkit guide --json` reports `app` and `replaced`.
|
|
18
|
+
|
|
19
|
+
## 0.4.0 — 2026-10-23
|
|
20
|
+
|
|
21
|
+
**Guides.** `mkit guide` reads the guides every app follows — the About screen,
|
|
22
|
+
in-app review, in-app updates, ASO, support replies, app UI, the store folder,
|
|
23
|
+
store art, news notifications — filled in with your studio's details from
|
|
24
|
+
`[studio]`, `[about]` and `[support]` in `~/.mkit/config.toml` or an app's
|
|
25
|
+
`mkit.toml`. They ship inside the package, so they work offline. Anything not
|
|
26
|
+
set shows as ‹key› and is listed; `--raw` gives the guide as written.
|
|
27
|
+
|
|
28
|
+
- **The About screen is yours to shape.** `[about] parts` picks which of its
|
|
29
|
+
nine parts an app has, and every line it says has an `[about]` key with a
|
|
30
|
+
default.
|
|
31
|
+
- **Your own rules beside a guide.** A file of the same name in
|
|
32
|
+
`[guide] house_dir` is appended under *This studio's own rules*, and wins
|
|
33
|
+
where the two disagree.
|
|
34
|
+
- Five guides are new to this repo: about-screen, in-app-review, aso,
|
|
35
|
+
support-replies and app-ui.
|
|
36
|
+
|
|
37
|
+
**`--dry-run` can no longer quietly make the change.** `mkit init --dry-run`
|
|
38
|
+
wrote every file it was meant to preview, and twelve other write-class commands
|
|
39
|
+
ignored the flag the same way: `bump` committed, `share` uploaded, `firebase
|
|
40
|
+
sha-add` changed the Firebase project. Commands now declare whether they
|
|
41
|
+
support a dry run, and the CLI refuses `--dry-run` for any that do not — exit 2,
|
|
42
|
+
`NO_DRY_RUN` — before anything runs. `init` and `bump` preview for real. Every
|
|
43
|
+
publish-class command already honoured it and still does.
|
|
44
|
+
|
|
45
|
+
- `mkit commands --json` reports `dry_run` per command, so an agent can tell in
|
|
46
|
+
advance which commands it can rehearse.
|
|
47
|
+
|
|
10
48
|
## 0.3.1 — first public release
|
|
11
49
|
|
|
12
50
|
mkit shipped a small studio's Android apps for a year before this; this is that
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mkit-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Release Android apps to Google Play from the command line: build, upload, listing, screenshots, data safety, in-app products, vitals and Firebase, in one tool built for humans and AI agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/almasumdev/mkit-cli
|
|
6
6
|
Project-URL: Documentation, https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md
|
|
@@ -137,6 +137,29 @@ Some things have no API and must be done in the Play Console. `mkit manual`
|
|
|
137
137
|
lists them, so you know what is left rather than discovering it at release
|
|
138
138
|
time.
|
|
139
139
|
|
|
140
|
+
## Guides
|
|
141
|
+
|
|
142
|
+
Some things every app has to do, and every app that works them out alone does
|
|
143
|
+
differently: an About screen, asking for a rating, checking for updates, being
|
|
144
|
+
found on Play, answering users. mkit carries a guide for each, and fills it in
|
|
145
|
+
with your studio's details:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
mkit guide # the list
|
|
149
|
+
mkit guide about # the About screen, with your email, developer page, links
|
|
150
|
+
mkit guide review # when to ask for a rating, and what Play forbids around it
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Set your details once in `~/.mkit/config.toml` — `[studio]` for the facts,
|
|
154
|
+
`[about]` for which parts your About screen has and what it says — and every
|
|
155
|
+
guide, for every app, uses them. Anything not set shows as ‹key› and is listed.
|
|
156
|
+
Keep your own rules beside a guide in `[guide] house_dir`; they are appended
|
|
157
|
+
and win where they disagree. The keys are in
|
|
158
|
+
[docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md#studio-details-mkit-guide).
|
|
159
|
+
|
|
160
|
+
They are written so an AI agent building the screen can follow them too:
|
|
161
|
+
`mkit guide about --json` returns the text.
|
|
162
|
+
|
|
140
163
|
## Working with AI agents
|
|
141
164
|
|
|
142
165
|
mkit answers in JSON whenever it is not attached to a terminal, with one
|
|
@@ -151,6 +174,11 @@ and what an agent must never do.
|
|
|
151
174
|
|---|---|
|
|
152
175
|
| [docs/commands.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md) | every command and flag |
|
|
153
176
|
| [docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md) | `mkit.toml`, the store and keyword files, credentials |
|
|
177
|
+
| [docs/about-screen.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/about-screen.md) | the About screen: its parts, words, marks and behaviour (`mkit guide about`) |
|
|
178
|
+
| [docs/in-app-review.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-review.md) | asking for a rating: when, where, and the rules Play enforces (`mkit guide review`) |
|
|
179
|
+
| [docs/aso.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/aso.md) | being found on Play: the indexed fields, winnable keywords, the title |
|
|
180
|
+
| [docs/support-replies.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/support-replies.md) | answering users by email |
|
|
181
|
+
| [docs/app-ui.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/app-ui.md) | how an app is organised: colours, Pro, pickers, Settings |
|
|
154
182
|
| [docs/store.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store.md) | the `store/` folder: what lives where, in every language |
|
|
155
183
|
| [docs/store-art.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store-art.md) | screenshots and the feature graphic, built from real captures |
|
|
156
184
|
| [docs/in-app-update.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-update.md) | how an app should check for updates: Play first, your policy second |
|
|
@@ -105,6 +105,29 @@ Some things have no API and must be done in the Play Console. `mkit manual`
|
|
|
105
105
|
lists them, so you know what is left rather than discovering it at release
|
|
106
106
|
time.
|
|
107
107
|
|
|
108
|
+
## Guides
|
|
109
|
+
|
|
110
|
+
Some things every app has to do, and every app that works them out alone does
|
|
111
|
+
differently: an About screen, asking for a rating, checking for updates, being
|
|
112
|
+
found on Play, answering users. mkit carries a guide for each, and fills it in
|
|
113
|
+
with your studio's details:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
mkit guide # the list
|
|
117
|
+
mkit guide about # the About screen, with your email, developer page, links
|
|
118
|
+
mkit guide review # when to ask for a rating, and what Play forbids around it
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Set your details once in `~/.mkit/config.toml` — `[studio]` for the facts,
|
|
122
|
+
`[about]` for which parts your About screen has and what it says — and every
|
|
123
|
+
guide, for every app, uses them. Anything not set shows as ‹key› and is listed.
|
|
124
|
+
Keep your own rules beside a guide in `[guide] house_dir`; they are appended
|
|
125
|
+
and win where they disagree. The keys are in
|
|
126
|
+
[docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md#studio-details-mkit-guide).
|
|
127
|
+
|
|
128
|
+
They are written so an AI agent building the screen can follow them too:
|
|
129
|
+
`mkit guide about --json` returns the text.
|
|
130
|
+
|
|
108
131
|
## Working with AI agents
|
|
109
132
|
|
|
110
133
|
mkit answers in JSON whenever it is not attached to a terminal, with one
|
|
@@ -119,6 +142,11 @@ and what an agent must never do.
|
|
|
119
142
|
|---|---|
|
|
120
143
|
| [docs/commands.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md) | every command and flag |
|
|
121
144
|
| [docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md) | `mkit.toml`, the store and keyword files, credentials |
|
|
145
|
+
| [docs/about-screen.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/about-screen.md) | the About screen: its parts, words, marks and behaviour (`mkit guide about`) |
|
|
146
|
+
| [docs/in-app-review.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-review.md) | asking for a rating: when, where, and the rules Play enforces (`mkit guide review`) |
|
|
147
|
+
| [docs/aso.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/aso.md) | being found on Play: the indexed fields, winnable keywords, the title |
|
|
148
|
+
| [docs/support-replies.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/support-replies.md) | answering users by email |
|
|
149
|
+
| [docs/app-ui.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/app-ui.md) | how an app is organised: colours, Pro, pickers, Settings |
|
|
122
150
|
| [docs/store.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store.md) | the `store/` folder: what lives where, in every language |
|
|
123
151
|
| [docs/store-art.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store-art.md) | screenshots and the feature graphic, built from real captures |
|
|
124
152
|
| [docs/in-app-update.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-update.md) | how an app should check for updates: Play first, your policy second |
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# The About screen
|
|
2
|
+
|
|
3
|
+
Every app needs an About screen, and every app that solves it on its own ends
|
|
4
|
+
up with a different one. This guide is a spine that works: which parts, in what
|
|
5
|
+
order, what each says and does, and what it must never claim. **Only the theme
|
|
6
|
+
changes between apps** — colours, type, and the app's own row and button
|
|
7
|
+
components.
|
|
8
|
+
|
|
9
|
+
Read it with `mkit guide about`. Your studio's details are filled in from
|
|
10
|
+
`[studio]`, and which parts you use and what they say from `[about]` — in
|
|
11
|
+
`~/.mkit/config.toml` for every app, or an app's own `mkit.toml` to differ.
|
|
12
|
+
Anything you have not set shows as ‹key›.
|
|
13
|
+
|
|
14
|
+
## Make it yours
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[studio]
|
|
18
|
+
name = "Acme Apps"
|
|
19
|
+
tagline = "Small tools, done properly."
|
|
20
|
+
email = "hello@acme.dev" # where users and clients write
|
|
21
|
+
website = "https://acme.dev"
|
|
22
|
+
privacy_url = "https://acme.dev/privacy"
|
|
23
|
+
terms_url = "https://acme.dev/terms"
|
|
24
|
+
play_developer_id = "1234567890123456789" # from your Play developer page URL
|
|
25
|
+
developer = "Ada Example"
|
|
26
|
+
developer_role = "Mobile app developer at Acme Apps"
|
|
27
|
+
portfolio = "https://ada.example"
|
|
28
|
+
photo = "https://github.com/ada-example.png?size=460"
|
|
29
|
+
|
|
30
|
+
[about]
|
|
31
|
+
# Leave out a part to drop it - its row, its words and its behaviour.
|
|
32
|
+
parts = ["mark", "name", "version", "what", "rating", "maker", "ask", "more_apps", "footer"]
|
|
33
|
+
# Every line below can be reworded; these are the defaults.
|
|
34
|
+
rating_heading = "Enjoying <App>?"
|
|
35
|
+
ask = "We design and build mobile apps. <App> is one of ours. If you have one in mind, tell us about it."
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`<App>` stands for the app's name, translated with the rest of the line.
|
|
39
|
+
|
|
40
|
+
## The spine
|
|
41
|
+
|
|
42
|
+
Top to bottom, in this order. A studio leaves out what it does not want;
|
|
43
|
+
nothing is reordered.
|
|
44
|
+
|
|
45
|
+
| # | Part | What it holds |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| 1 | **Mark** | the app's own launcher icon, centred, nothing behind it | <!-- part:mark -->
|
|
48
|
+
| 2 | **Name** | the app name, the largest type on the page | <!-- part:name -->
|
|
49
|
+
| 3 | **Version** | `v1.2.3` — the short form, bare, no tag or box, no build number | <!-- part:version -->
|
|
50
|
+
| 4 | **What it is** | one sentence, centred, the app's own words; never marketing | <!-- part:what -->
|
|
51
|
+
| 5 | **Rating** | a heading, one line, five stars → the Play listing | <!-- part:rating -->
|
|
52
|
+
| 6 | **The maker** | photo, name, role → the portfolio | <!-- part:maker -->
|
|
53
|
+
| 7 | **The ask** | a few lines about the studio, then two actions: email, website | <!-- part:ask -->
|
|
54
|
+
| 8 | **More apps** | a heading, one line, one Play button → the developer page | <!-- part:more_apps -->
|
|
55
|
+
| 9 | **Footer** | Privacy · Terms, then `© <year> {{studio.name}}` | <!-- part:footer -->
|
|
56
|
+
|
|
57
|
+
The first four are one block, centred. The middle parts are banks, each with a
|
|
58
|
+
heading and left-aligned text. The footer is centred and quiet. That single
|
|
59
|
+
change of alignment is what tells the eye where the reading starts.
|
|
60
|
+
|
|
61
|
+
**No cards.** Not for the rating, not for the maker, not for the Play button —
|
|
62
|
+
even in an app whose other screens are made of cards. Groups are made by space
|
|
63
|
+
alone: a heading sits close to what it names and far from what it does not, and
|
|
64
|
+
that asymmetry is the whole of the grouping. Roughly 10dp under a heading
|
|
65
|
+
against 26dp under a bank; wider — about 34dp — above the first bank and below
|
|
66
|
+
the last, where the page changes subject rather than section. An About page is
|
|
67
|
+
a few short things in a column, and a container round each is how it turns into
|
|
68
|
+
a filing cabinet.
|
|
69
|
+
|
|
70
|
+
## The words
|
|
71
|
+
|
|
72
|
+
English below; translate them into each of the app's locales. Every line is an
|
|
73
|
+
`[about]` key, so a studio rewords it once for all its apps.
|
|
74
|
+
|
|
75
|
+
| String | Key | Text |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| What it is | — | the app's own one sentence — the only line it writes itself | <!-- part:what -->
|
|
78
|
+
| Rating heading | `rating_heading` | `{{about.rating_heading}}` | <!-- part:rating -->
|
|
79
|
+
| Rating line | `rating_line` | `{{about.rating_line}}` | <!-- part:rating -->
|
|
80
|
+
| Maker heading | `maker_heading` | `{{about.maker_heading}}` | <!-- part:maker -->
|
|
81
|
+
| Maker name / role | `[studio]` | `{{studio.developer}}` / `{{studio.developer_role}}` | <!-- part:maker -->
|
|
82
|
+
| The ask | `ask` | `{{about.ask}}` | <!-- part:ask -->
|
|
83
|
+
| Email button | `email_button` | `{{about.email_button}}` | <!-- part:ask -->
|
|
84
|
+
| Website button | `website_button` | `{{about.website_button}}` | <!-- part:ask -->
|
|
85
|
+
| More heading | `more_heading` | `{{about.more_heading}}` | <!-- part:more_apps -->
|
|
86
|
+
| More line | `more_line` | `{{about.more_line}}` | <!-- part:more_apps -->
|
|
87
|
+
| Play button | — | `Google Play` | <!-- part:more_apps -->
|
|
88
|
+
| Footer | — | `Privacy` · `Terms` · `© <year> {{studio.name}}` | <!-- part:footer -->
|
|
89
|
+
|
|
90
|
+
The buttons **name their destination and nothing else**. The sentence saying
|
|
91
|
+
what is over there goes above them. A button carrying the whole explanation is
|
|
92
|
+
a sentence with a button drawn round it.
|
|
93
|
+
|
|
94
|
+
An app's own heading style still applies: if its section headers are uppercased
|
|
95
|
+
by its own component, these are too. The words are shared; the case is the
|
|
96
|
+
app's.
|
|
97
|
+
|
|
98
|
+
## What it never does
|
|
99
|
+
|
|
100
|
+
- **No rating it does not have.** The stars carry no number and show nothing
|
|
101
|
+
back. They are a link to the listing, not a score.
|
|
102
|
+
- **No install counts, no review counts, no awards, no "loved by N users".**
|
|
103
|
+
- **No "All rights reserved."** It has carried no legal weight since the Berne
|
|
104
|
+
Convention. The bare `© <year> {{studio.name}}` stays, and the year is read
|
|
105
|
+
from the clock so the line cannot rot.
|
|
106
|
+
- **No list of the studio's other apps inside the app.** A hard-coded list
|
|
107
|
+
cannot know what shipped after the build, what was renamed or what was
|
|
108
|
+
pulled, and each of those becomes a false line on a page whose whole claim is
|
|
109
|
+
that it states facts. The Play developer page is the same information, kept
|
|
110
|
+
by the shop that sells it.
|
|
111
|
+
- **No second button saying what the first one does.** A "Rate on Google Play"
|
|
112
|
+
button under five stars that do exactly that asks twice.
|
|
113
|
+
|
|
114
|
+
## The marks
|
|
115
|
+
|
|
116
|
+
One shape per job, at one weight. Take them from the app's own outline icon set.
|
|
117
|
+
|
|
118
|
+
| Where | Mark |
|
|
119
|
+
|---|---|
|
|
120
|
+
| A row that navigates | chevron pointing forward |
|
|
121
|
+
| Email button | envelope | <!-- part:ask -->
|
|
122
|
+
| Website button | globe | <!-- part:ask -->
|
|
123
|
+
| Google Play button | Google Play's own four-colour mark, from Google's brand resources — never redrawn | <!-- part:more_apps -->
|
|
124
|
+
| Rating | five hollow stars | <!-- part:rating -->
|
|
125
|
+
|
|
126
|
+
Outline weight throughout, one family throughout. A filled glyph beside a hollow
|
|
127
|
+
one is the fastest way to make a page look assembled from whatever was to hand.
|
|
128
|
+
The Play mark is the single exception to the app's palette and is **never
|
|
129
|
+
tinted**: it is a trademark, and an icon slot that paints its content with the
|
|
130
|
+
surface's colour would flatten all four of its colours into one.
|
|
131
|
+
|
|
132
|
+
## What changes per app
|
|
133
|
+
|
|
134
|
+
A short list on purpose. Read the app's design doc, or its theme file, and take
|
|
135
|
+
four things from it:
|
|
136
|
+
|
|
137
|
+
1. **Every colour** — the ground, the ink, and a warm tone for the stars.
|
|
138
|
+
2. **The type scale**, and which style is a heading.
|
|
139
|
+
3. **The row it already uses** for "tap to go somewhere".
|
|
140
|
+
4. **The button it already uses** for a primary action.
|
|
141
|
+
|
|
142
|
+
Two rules hold whatever those are:
|
|
143
|
+
|
|
144
|
+
1. **Reuse the app's own components.** If its settings list has a row, the
|
|
145
|
+
About page uses that row; a page built from new one-off widgets is how two
|
|
146
|
+
screens in one app stop looking related. Reuse the *component*, not a
|
|
147
|
+
container: a card the app has is still a card and still does not belong here.
|
|
148
|
+
2. **Keep the app's colour discipline.** Where an app gives colours meanings,
|
|
149
|
+
this page obeys them and adds no new accent. If its palette has no gold for
|
|
150
|
+
the stars, take the nearest warm tone it already has.
|
|
151
|
+
|
|
152
|
+
## The two assets that fool you
|
|
153
|
+
|
|
154
|
+
**The app icon.** An adaptive icon is drawn on a canvas bigger than the mark on
|
|
155
|
+
it — the safe-zone margin a launcher masks away. Drawn plainly it arrives with a
|
|
156
|
+
ring of transparent air that reads as a hole, and no spacer around it fixes that,
|
|
157
|
+
because the air is inside the picture. Measure the mark in the vector's own path
|
|
158
|
+
data, then draw the canvas at `mark_size × viewport / mark_height` inside a box
|
|
159
|
+
the size of the mark, and let the margin overflow. A mark running 21→87 of a 108
|
|
160
|
+
viewport, wanted at 72dp, needs a 118dp canvas. In Compose that overflow needs
|
|
161
|
+
`requiredSize`, not `size`, or the box's constraints shrink it back.
|
|
162
|
+
|
|
163
|
+
**No tile behind it** — not the icon's own background layer, not a rounded plate.
|
|
164
|
+
A filled tile is a card. That leaves two layers, and which one is a contrast
|
|
165
|
+
question, not a matter of taste:
|
|
166
|
+
|
|
167
|
+
1. **The foreground alone**, where its artwork carries its own colour and that
|
|
168
|
+
colour reads on both of the app's grounds.
|
|
169
|
+
2. **The monochrome layer, tinted with the page's ink**, where the foreground
|
|
170
|
+
was drawn for the icon's own background and disappears without it — a
|
|
171
|
+
near-black foreground on a dark theme is the same colour twice. The
|
|
172
|
+
monochrome layer is that shape with the colour taken out, so it takes the
|
|
173
|
+
page's ink and reads on both themes.
|
|
174
|
+
|
|
175
|
+
Check the foreground against the app's darkest **and** lightest surface before
|
|
176
|
+
choosing. Both layers carry the same margin, so size either off the mark.
|
|
177
|
+
|
|
178
|
+
**The photo.** One square image, centre-cropped to a circle, never letterboxed
|
|
179
|
+
into it. 256px is enough for a 46–52dp avatar. Take it from `{{studio.photo}}`
|
|
180
|
+
and store it density-independent (`drawable-nodpi` on Android, a single asset in
|
|
181
|
+
Flutter).
|
|
182
|
+
|
|
183
|
+
## Behaviour
|
|
184
|
+
|
|
185
|
+
| Control | Does |
|
|
186
|
+
|---|---|
|
|
187
|
+
| Any star | the app's own Play listing: `market://details?id=<package>`, then `https://play.google.com/store/apps/details?id=<package>` | <!-- part:rating -->
|
|
188
|
+
| Maker row | `{{studio.portfolio}}` | <!-- part:maker -->
|
|
189
|
+
| Email button | `mailto:{{studio.email}}`, with a subject naming the app | <!-- part:ask -->
|
|
190
|
+
| Website button | `{{studio.website}}` | <!-- part:ask -->
|
|
191
|
+
| Google Play button | `market://dev?id={{studio.play_developer_id}}`, then `https://play.google.com/store/apps/dev?id={{studio.play_developer_id}}` | <!-- part:more_apps -->
|
|
192
|
+
| Privacy, Terms | `{{studio.privacy_url}}`, `{{studio.terms_url}}` | <!-- part:footer -->
|
|
193
|
+
|
|
194
|
+
Every one of these hands the user to something the app does not own, so every
|
|
195
|
+
one can fail: no mail app, no browser, no Play. Fail in silence — there is no
|
|
196
|
+
second way to do any of them, and a toast saying the phone has no email client
|
|
197
|
+
helps nobody.
|
|
198
|
+
|
|
199
|
+
For when a rating is *asked for* rather than offered here — the prompt at a
|
|
200
|
+
good moment, and what happens after a low score — see `mkit guide review`.
|
|
201
|
+
|
|
202
|
+
## Strings
|
|
203
|
+
|
|
204
|
+
Every line except the studio's own names is translated into all of the app's
|
|
205
|
+
locales in the same change that adds it. An About page that is half English is
|
|
206
|
+
worse than one that is plainly smaller. These stay untranslated
|
|
207
|
+
(`translatable="false"` on Android): the developer's name, the studio name, the
|
|
208
|
+
website's address, and `Google Play` — a product name Google requires to be left
|
|
209
|
+
in English.
|
|
210
|
+
|
|
211
|
+
## In Flutter
|
|
212
|
+
|
|
213
|
+
The same spine maps onto the app's own widgets: `package_info_plus` for the
|
|
214
|
+
version, `url_launcher` for every row in the behaviour table, the launcher icon
|
|
215
|
+
from the app's own asset, and the app's existing section header, list row and
|
|
216
|
+
button widgets for everything below the centred block.
|
|
217
|
+
|
|
218
|
+
## Checklist for a new app
|
|
219
|
+
|
|
220
|
+
1. Set `[studio]` and `[about]` once; check them with `mkit guide about`.
|
|
221
|
+
2. Read the app's design doc and take the four things above from it.
|
|
222
|
+
3. Build the spine with that app's components — no new widgets unless the app
|
|
223
|
+
genuinely lacks one, and no cards.
|
|
224
|
+
4. App icon sized off its own artwork; photo cropped to a circle.
|
|
225
|
+
5. All strings in every locale the app ships.
|
|
226
|
+
6. Wire the behaviours; let each fail silently.
|
|
227
|
+
7. Check it in the app's light and dark themes, and in its right-to-left locale
|
|
228
|
+
if it has one.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# App UI foundations
|
|
2
|
+
|
|
3
|
+
This decides how an app is organised, never how it looks: where colours
|
|
4
|
+
live, how Pro is marked, what a picker does, how Settings is put together. No
|
|
5
|
+
colour value (except Pro's gold) and no size of the app's look is set here;
|
|
6
|
+
each app keeps its own palette, type, shapes and components. A studio whose
|
|
7
|
+
apps share one look keeps that look in its own rules beside this guide.
|
|
8
|
+
|
|
9
|
+
Read this before adding a colour, a Pro mark, a settings row or a picker to
|
|
10
|
+
any app. Read it with `mkit guide ui`.
|
|
11
|
+
|
|
12
|
+
## Colours from one place
|
|
13
|
+
|
|
14
|
+
Every colour an app draws is defined in its theme folder and nowhere else.
|
|
15
|
+
|
|
16
|
+
| Kind | Where |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Follows the theme and palette | the palette file: one complete set per palette, light and dark |
|
|
19
|
+
| Never changes with the theme | `fixed_colors.dart` (or the native app's equivalent) |
|
|
20
|
+
| The framework's own (Material `ColorScheme`, Compose `ColorScheme`) | mapped from the palette set by the theme builder |
|
|
21
|
+
|
|
22
|
+
Widgets read colours through the app's token accessor (a context extension in
|
|
23
|
+
Flutter, a `CompositionLocal` in Compose) and the framework's widgets get them
|
|
24
|
+
through the colour scheme the builder maps them into. A widget never writes a
|
|
25
|
+
colour literal: in Flutter no `Color(0x…)`, `Color.fromRGBO`, `Color.fromARGB`
|
|
26
|
+
or `Colors.<name>` (only `Colors.transparent`); in Compose no `Color(0x…)`.
|
|
27
|
+
|
|
28
|
+
**The rule is a test, not a convention.** Each app carries one that walks its
|
|
29
|
+
source, skips the theme folder, generated files and comment lines, and fails
|
|
30
|
+
on any colour literal, naming the file and line. Write it once and copy it to
|
|
31
|
+
each app, changing only the folder name.
|
|
32
|
+
|
|
33
|
+
**One complete set per palette.** Every palette, in light and in dark, is one
|
|
34
|
+
full colour set: the ground, the inks from strongest to faintest, the card,
|
|
35
|
+
the lines, the primary fill and the ink on it, the icon tile and its glyph,
|
|
36
|
+
the accent and its soft wash, danger, the warning colour, and whatever else
|
|
37
|
+
the app draws with (a receipt's paper, a panel's gradient). The theme builder
|
|
38
|
+
and the token class only map that set; neither writes a colour of its own. An
|
|
39
|
+
app with one palette has one set per brightness, and the same rule.
|
|
40
|
+
|
|
41
|
+
**Two inks for the primary colour.** A primary colour dark enough to carry
|
|
42
|
+
white text as a button is often too dark to read as text on a dark ground.
|
|
43
|
+
So a set carries an ink for the primary colour used as text or an icon (a
|
|
44
|
+
price, a check, a cursor, a focused field, a text button), and one for the
|
|
45
|
+
glyph on an icon tile, separate from the fill. Fills (buttons, switches, a
|
|
46
|
+
selected dot) keep the primary. Where the two are the same colour, the set
|
|
47
|
+
simply says so twice.
|
|
48
|
+
|
|
49
|
+
What goes in `fixed_colors.dart`, and nothing else: the brand (a mascot, the
|
|
50
|
+
intro slides), Pro's gold, other companies' marks in their own colours,
|
|
51
|
+
shadows and scrims, and colours fixed by what they sit on (white on a red
|
|
52
|
+
swipe, ink on a photo). A colour that ought to change with the palette is in
|
|
53
|
+
the palette set, even if today it happens to be the same in every palette.
|
|
54
|
+
|
|
55
|
+
## Pro's mark
|
|
56
|
+
|
|
57
|
+
Pro is the studio's product, so its mark is the same in every app: **one
|
|
58
|
+
crown, drawn, in one gold.**
|
|
59
|
+
|
|
60
|
+
- **The shape** is one crown, drawn in code on a 24-unit
|
|
61
|
+
grid, never an icon font's crown (`workspace_premium` and the like): three
|
|
62
|
+
points with round tips over a band, solid, one colour, the jewel and the gap
|
|
63
|
+
above the band cut through it (even-odd).
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
body (even-odd): M3.6 8.2 7.7 11.7 12 5.4 16.3 11.7 20.4 8.2 18.7 16.2H5.3Z
|
|
67
|
+
M12 11.2 13.6 13 12 14.8 10.4 13Z
|
|
68
|
+
tips: circles r1.6 at (3.6, 6.9) and (20.4, 6.9), r1.7 at (12, 3.9)
|
|
69
|
+
band: rounded rect 4.8,18 → 19.2,20.8, corner 1.4
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
One component per app draws it: an icon widget in Flutter, a composable in
|
|
73
|
+
Compose.
|
|
74
|
+
- **The colour** is Pro gold — this guide uses `#E3A93C` — a fixed colour, the same in light
|
|
75
|
+
and dark and in every palette: a mark of the product that changed with the
|
|
76
|
+
theme would read as two marks.
|
|
77
|
+
- **The tag** beside anything the free plan lacks is an outline, never a
|
|
78
|
+
fill: the crown and the word `PRO` in gold inside a gold-outlined pill, in
|
|
79
|
+
the app's own type. Shown only while Pro is not owned.
|
|
80
|
+
- **Where the crown stands alone:** a locked colour theme's swatch, a locked
|
|
81
|
+
action's icon, the Pro screen's header, in gold. Inside a filled button it
|
|
82
|
+
takes the button's own ink instead. On a row whose other rows have icon
|
|
83
|
+
tiles, it sits on the row's usual tile in the tile's glyph colour, so it
|
|
84
|
+
lines up with the rest.
|
|
85
|
+
|
|
86
|
+
## Settings
|
|
87
|
+
|
|
88
|
+
- **Groups under section headers.** Rows that belong together form a group;
|
|
89
|
+
groups sit under short section headers in the app's own style.
|
|
90
|
+
- **The Pro row** comes first, on its own, with the crown as its icon.
|
|
91
|
+
- **The rating, share and feedback rows** are those of `mkit guide review`;
|
|
92
|
+
the update row and the version line at the very foot are those of
|
|
93
|
+
`mkit guide update`.
|
|
94
|
+
- **An app with colour themes** puts the light/dark choice and the colour
|
|
95
|
+
choice together, one after the other in the same place. A locked colour can
|
|
96
|
+
be tried on before buying; leaving Settings ends the try-on.
|
|
97
|
+
|
|
98
|
+
## Picker sheets
|
|
99
|
+
|
|
100
|
+
A choice from a list (a language, a currency, a list to move to) is a
|
|
101
|
+
**bottom sheet**; a question with a few answers is a dialog
|
|
102
|
+
(`mkit guide review`).
|
|
103
|
+
|
|
104
|
+
- **One frame** for every sheet in the app: its handle, its title, its body.
|
|
105
|
+
One component, not one per sheet.
|
|
106
|
+
- **A long list has search.** Any picker with more than about a dozen
|
|
107
|
+
choices (languages, currencies) opens with a search field and stays clear of
|
|
108
|
+
the keyboard. Matching: a language by its own name, its English name or a
|
|
109
|
+
code prefix; a currency by code, sign, name or country. Nothing found is one
|
|
110
|
+
plain line saying so, not an empty sheet.
|
|
111
|
+
- **The chosen row is marked one way in every picker** of the app, in its own
|
|
112
|
+
colours (a wash, a check, a bolder title: whichever the app chose, the same
|
|
113
|
+
everywhere). Two pickers that mark it two ways is the bug.
|
|
114
|
+
- Tapping a row applies it and closes the sheet; there is no Done button.
|
|
115
|
+
|
|
116
|
+
## Checklist, every app
|
|
117
|
+
|
|
118
|
+
1. No colour literal outside the theme folder, and the test that proves it.
|
|
119
|
+
2. One complete colour set per palette and brightness; the builder only maps.
|
|
120
|
+
3. Pro gold in `fixed_colors.dart`, the drawn crown everywhere Pro is marked,
|
|
121
|
+
no icon-font crown left, and the Pro tag an outline.
|
|
122
|
+
4. Language and currency pickers search, and every picker marks the choice
|
|
123
|
+
the same way.
|