Skip to content

Commit bf309e0

Browse files
Merge pull request #41: Android 1.6.0 — 2026 design, collections and article parity
Restyle Android and complete collection and article parity
2 parents 1865005 + 06d55f9 commit bf309e0

101 files changed

Lines changed: 4193 additions & 1322 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎DESIGN.md‎

Lines changed: 268 additions & 64 deletions
Large diffs are not rendered by default.

‎PRODUCT.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ Library members on their own phone, mostly in daylight or normal indoor light,
1414
in a calm "browsing / reading" mood. Not power users; not a dark-room tool.
1515

1616
## Brand & tone
17-
Pinakes. One signature colour: **magenta `#D70161`** (the palm logo). Tone:
17+
Pinakes. Default accent: **magenta `#D70161`** (the palm logo); other library themes supply their own accent. Tone:
1818
quiet, precise, trustworthy, a little warm. Think a good reading app, not a
1919
flashy consumer social app.
2020

2121
## Strategic principles
2222
- **Minimal, not decorated.** Content (book covers, titles, availability) is the
2323
hero. Chrome recedes.
24-
- **One accent.** Magenta is the only colour with meaning: primary actions,
24+
- **One accent per library theme.** It marks primary actions,
2525
current selection, key status. Everything else is a clean neutral. No second
2626
brand hue, no indigo/violet, no rainbow chips.
2727
- **Readable first.** Real contrast on every label and field. Never colour-on-
@@ -30,8 +30,8 @@ flashy consumer social app.
3030
surface. Dark is available, not assumed.
3131

3232
## Anti-references (what this must NOT look like)
33-
- Gradients of any kind (hero banners, headers, buttons). Banned.
33+
- Decorative gradients on buttons, text or cards. The subtle 2026 hero wash and the book spine/gloss are the deliberate exceptions.
3434
- Purple/indigo selection states. The "random colours" look. Banned.
35-
- Warm brown/dun surfaces. Neutrals stay cool and clean.
35+
- Heavy brown surfaces. The 2026 warm off-white neutrals stay light and quiet.
3636
- Decorated circular logo badges, ringed avatars, heavy cards everywhere.
3737
- "AI slop": a washed-out gradient panel with light text on a light field.

‎README.md‎

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
Native Android client for a [Pinakes](https://github.com/fabiodalez-dev/Pinakes)
44
library instance. Browse the catalog, check real availability, borrow and reserve
55
books, read ebooks and listen to audiobooks, and manage your loans, all from your
6-
phone.
6+
phone. Optional Archives, Desiderata and analytic articles are available when the
7+
server advertises their APIs. The five bottom navigation destinations are retained.
78

89
![Platform](https://img.shields.io/badge/platform-Android-3DDC84)
910
![minSdk](https://img.shields.io/badge/minSdk-26-blue)
@@ -30,8 +31,14 @@ The app is server-agnostic: it works against any Pinakes instance that has the
3031
Mobile API enabled, and it adapts to that instance's settings (language,
3132
catalogue-only mode, push availability).
3233

34+
## 2026 interface
35+
36+
The Android interface follows the public Pinakes 2026 restyle: shared theme tokens, Geist for controls, Fraunces for titles, fitted book artwork and a quiet warm background. `ThemePalette` accepts library colours and hero/card styles; Classic / Covers are the built-in defaults until the Mobile API exposes theme settings. Existing circulation, account and plugin flows remain available. See [DESIGN.md](DESIGN.md) for the component contract and API limitations.
37+
3338
## Screenshots
3439

40+
Login, home, catalog, book detail and calendar captures use the 2026 interface on Android 15 at 360 dp, with fixture data. The remaining account captures document the earlier flow layout.
41+
3542
<table>
3643
<tr>
3744
<td align="center"><img src="docs/screenshots/login.png" width="200" alt="Login screen"><br><sub>Login</sub></td>
@@ -53,10 +60,10 @@ catalogue-only mode, push availability).
5360
|------|--------------|
5461
| **Onboarding** | Enter the instance URL, `/health` discovery card (library name, logo, HTTPS check, mobile-access check) |
5562
| **Sign in & sign up** | Email/password login, **in-app registration** and **password recovery**, mapped error messages, secure token storage |
56-
| **Home** | An "Available now" landing showing what you can borrow today |
57-
| **Catalog** | The full catalog with infinite scroll, search, and a filter sheet (availability, genre, author, publisher, language) |
58-
| **Book detail** | HTML-rendered description, tap-to-zoom cover, full metadata block (ISBN, year, pages …), genre chip |
59-
| **Availability** | Colour-coded state: green available, red on loan, amber reserved |
63+
| **Home** | Searchable library hero, a fan of real shelf covers, and an "Available now" / recent shelf |
64+
| **Catalog** | Two-column book grid or compact list, infinite scroll, search, sort and a filter sheet (availability, genre, author, publisher, language) |
65+
| **Book detail** | HTML-rendered description, tap-to-zoom cover, full metadata block (ISBN, year, pages …), genre hierarchy |
66+
| **Availability** | Neutral status pills with a coloured dot: green available, red on loan, amber reserved |
6067
| **Loan calendar** | Pick a start date on a calendar that paints already-booked days and pre-selects the first free day |
6168
| **Audiobooks** | In-app player (Media3 ExoPlayer) when the title has an audio file |
6269
| **Ebooks** | In-app PDF reader (PdfRenderer); other formats open externally |
@@ -65,13 +72,13 @@ catalogue-only mode, push availability).
6572
| **Book Club** | When the instance runs the **Book Club** plugin: browse your clubs and the directory, open a club's reading list / polls / meetings, join, vote in-app (simple / multi / weighted ballots), RSVP to meetings and track your reading progress — advanced poll modes and proposing a title deep-link to the web |
6673
| **Profile** | Edit profile, change password, device list, theme switcher, language switcher, logout |
6774
| **Notifications** | Loan due/overdue, reservation ready, book available |
68-
| **Themes** | Material 3 light and dark, light by default; pick light/dark/system in Profile |
75+
| **Themes** | 2026 warm neutrals, Geist + Fraunces, complete 3D covers and paired theme colours. Light by default; pick light/dark/system in Profile |
6976
| **Languages** | Italian, English, French, German, following the device locale or an in-app choice |
7077

7178
## Tech stack
7279

7380
- **Kotlin 2.0** + **Jetpack Compose** (Material 3), single-module app
74-
- **Navigation-Compose** + `ViewModel`/`StateFlow`, manual DI via a `ServiceLocator`
81+
- **Navigation-Compose** + `ViewModel`/`StateFlow` + Hilt
7582
- **Retrofit + OkHttp + kotlinx.serialization** for the `/api/v1` client (`{data, meta, error}` envelope)
7683
- **Coil** for cover images, **Media3 ExoPlayer** for audio, platform `PdfRenderer` for PDFs
7784
- **AndroidX Security** (`EncryptedSharedPreferences`) for the bearer token and instance URL
@@ -89,6 +96,9 @@ catalogue-only mode, push availability).
8996
```bash
9097
./gradlew assembleDebug # debug APK → app/build/outputs/apk/debug/app-debug.apk
9198
./gradlew lintDebug # static analysis
99+
./gradlew testDebugUnitTest
100+
./gradlew connectedDebugAndroidTest # UI regression tests, with a running emulator
101+
./gradlew assembleRelease # R8 + resource shrinking, unsigned without release credentials
92102
```
93103

94104
Create a `local.properties` with `sdk.dir=/path/to/android-sdk` (or set `ANDROID_HOME`).
@@ -102,6 +112,29 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
102112

103113
A prebuilt debug APK is published on the [Releases](../../releases) page.
104114

115+
### Standalone emulator on macOS
116+
117+
```bash
118+
./tools/run-emulator.sh my_avd -no-snapshot-load -gpu auto
119+
```
120+
121+
The launcher forwards the remaining options to the Android SDK emulator. On macOS,
122+
it holds a `caffeinate` assertion for that emulator PID, then releases it when the
123+
emulator exits; Ctrl+C stops both. This reduces host power throttling during local
124+
testing. It does not change global power
125+
settings or Android crash reporting.
126+
127+
An Android 15 startup ANR was reproduced before application initialization, along
128+
with system/launcher stalls. With the same debug APK, host priority dropped to 4
129+
without the assertion and startup exceeded 21 seconds; with the assertion, three
130+
cold starts completed in 2.3–3.1 seconds. After restarting the VM through this
131+
launcher, five more cold starts completed in 1.66–1.80 seconds with no ANR events.
132+
An older AVD still stalled after a cold boot, and a restored snapshot retained a
133+
system-not-responding dialog. The assertion alone does not repair an unhealthy
134+
AVD; use a fresh development AVD without deleting needed user data. This is a
135+
development-emulator mitigation;
136+
device ANRs still require their own [trace diagnosis](https://developer.android.com/topic/performance/anrs/diagnose-and-fix-anrs).
137+
105138
## Point it at a Pinakes instance
106139

107140
On first launch the app asks for the instance URL.
@@ -188,6 +221,27 @@ Released under the same license as Pinakes: **AGPL-3.0**.
188221

189222
On compatible servers, **Emeroteca → Articles** searches and displays standalone
190223
newspaper and magazine articles, without requiring ownership of their issues.
191-
Publication screens also link to their associated articles. Public PDFs use the
192-
URL supplied by the server. Older servers retain the existing periodicals browser.
224+
Publication screens also link to their associated articles. Article covers, subtitles
225+
and published online resources follow the server data. Public PDFs use the
226+
URL supplied by the server; the website action opens the article’s full page. Older servers retain the existing periodicals browser.
193227
See [the article integration notes](docs/emeroteca-standalone-articles.md).
228+
229+
## Android 1.6 / current server parity
230+
231+
The updated client reads Mobile API 1.5.0, Desiderata 1.2.0, Emeroteca 1.13.0 and
232+
Archives 1.5.1. Update the server/plugins before expecting the new optional
233+
collection screens. Older instances remain usable; unsupported article facets
234+
show an upgrade message rather than silently returning unfiltered results.
235+
236+
Desiderata is the library's wanted collection, separate from a member's wishlist.
237+
Donation contact details come from the verified account. Proposals require
238+
consent and survive transport retries/process death without creating inventory.
239+
Archives supports paged hierarchy, text/level/year search, authorities, all
240+
public documents and export links. Article/book citations are formatted by the
241+
server, with five styles and text/HTML clipboard copies. Every digital book
242+
attachment is shown; audio switches one native player at a time.
243+
244+
Administrative cataloguing and uploads open the protected PHP website, including
245+
the existing article form. No native administrative CRUD API is invented.
246+
247+
See [the request-by-request Uwe verification](docs/UWE-PARITY.md).

‎STATUS.md‎

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# Pinakes Android — Build Status
22

3-
**Build: GREEN.** `./gradlew assembleDebug` succeeds; `./gradlew lintDebug` passes (0 errors). Kotlin compiles clean.
3+
## 2026 restyle (2026-10-08)
44

5-
- **APK:** `pinakes-debug.apk` (repo root, ~20 MB) — copied from `app/build/outputs/apk/debug/app-debug.apk`.
6-
Install: `adb install -r pinakes-debug.apk`.
7-
- **Package:** `com.pinakes.app` · versionName `1.0` · minSdk 26 · target/compileSdk 35 · launchable `MainActivity`.
8-
- **Verified on an emulator against a live Pinakes instance** (Android 15 / API 35 AVD → `http://10.0.2.2:8081`):
9-
onboarding → `/health` discovery → login (`200`) → catalog search with real books → book cards with
10-
correct titles/authors/availability. Two real bugs were found and fixed during this smoke test — see
11-
**Fixes applied** below. The app is **fully localized in 4 languages** (German verified live) — see **i18n**.
5+
The app now uses the 2026 web design: Geist / Fraunces, warm neutrals, a server-ready `ThemePalette`, complete 3D book covers, a searchable home hero with a cover fan, catalog grid/list views, dot status pills, grouped availability/actions and one card per exposed digital asset. Shared tokens apply to all existing account and plugin screens. The five-section bottom navigation is retained. Article lists and details now show server-resolved covers, subtitles, published online resources and access conditions, with a website action for the existing staff management flows. Native collections require the additive Mobile API 1.5.0 server update; existing circulation and authentication routes are retained.
6+
7+
- Debug APK: `pinakes-debug.apk`, copied from `app/build/outputs/apk/debug/app-debug.apk` (generated locally and ignored by Git).
8+
- Package: `com.pinakes.app`, version `1.6.0` (17), minSdk 26, target/compileSdk 35.
9+
- Local verification: **185 unit tests, 19 Compose device tests, zero lint errors**, and successful debug + R8 release builds.
10+
- Verification commands: `assembleDebug`, `testDebugUnitTest`, `lintDebug`, `assembleRelease`, `assembleDebugAndroidTest`; the 19 device tests run through `am instrument` on a separate Android 15 AVD, preserving the authenticated demo device.
11+
- Unit tests cover the existing contracts and theme contrast/mixing. Compose device tests cover full tall artwork, missing metadata, grid actions, view selection, digital-file cards, narrow search placeholders, circulation actions theme pairings, reachable empty-home actions and query preservation during loading.
12+
- Release builds exercise R8 and resource shrinking. Without release credentials the output is unsigned; no store release is published by this change.
13+
- macOS standalone emulator startup: `tools/run-emulator.sh` keeps a power assertion scoped to the emulator PID. A control with the identical debug APK reproduced a 21-second startup timeout at host priority 4; three starts with the assertion completed in 2.3–3.1 seconds. After a VM restart through the launcher, five more cold starts completed in 1.66–1.80 seconds with no ANR events. The original startup ANR also coincided with system/launcher stalls. An older AVD still stalled on a later cold boot, and a snapshot retained a system ANR dialog: the assertion does not repair an unhealthy AVD. No Sentry events are suppressed, and no production-device ANR fix is claimed.
14+
- New native sections: Desiderata with verified-account donation proposals and durable retry recovery; Archives with hierarchy, filters, authorities, documents and exports; analytic articles with all bibliographic fields, five citation styles, rich clipboard, shared-author navigation and issue contributions. Home and catalog support author sorting; every digital attachment and publisher is shown. Staff management uses protected website pages. See [Uwe parity](docs/UWE-PARITY.md).
15+
- Headless Android 15 completed the device tests while older GUI AVDs stalled despite the scoped power assertion. GUI and headless emulator processes had different host scheduling priorities; this is development-environment evidence, not a production ANR fix.
16+
- `ThemePalette` defaults to Classic / Covers. Discovery does not expose theme, CMS home sections, richer catalog facets, CMS-driven home ordering or share data yet; these are documented as future API work in DESIGN.md.
1217

1318
## Install & point at an instance
1419

@@ -39,22 +44,21 @@ On first launch the app shows **Onboarding**: enter your Pinakes instance URL.
3944
| 8. Notifications | ✅ | Feed w/ per-type icons, read/unread styling, **pull-to-refresh** |
4045
| 9. Contact | ✅ | `POST /messages` subject+body form, success state |
4146

42-
- **Bottom nav:** Search / Library / Wishlist / Profile. **Nested routes:** Book Detail, Notifications, Contact.
43-
- **Design system:** Material 3 light **and** dark, brand magenta `#D70161` + indigo `#6366F1`,
44-
Inter (bundled), rounded cards, soft shadows, brand-gradient header on onboarding/login,
45-
navigation transitions + list/press animations, adaptive launcher icon.
46-
- **Architecture:** Navigation-Compose + ViewModel/StateFlow, manual DI (`ServiceLocator` via a
47-
`LocalServices` composition local), Retrofit + OkHttp + kotlinx.serialization, Coil for covers.
47+
- **Bottom nav:** Home / Catalog / Library / Wishlist / Profile. **Nested routes:** Book Detail, Notifications, Contact and optional collection details.
48+
- **Design system:** Material 3 light **and** dark, 2026 theme-derived colours (Classic magenta by default),
49+
bundled Geist / Fraunces, warm neutrals, complete book covers, rounded controls and subtle hero washes.
50+
Calendar and status colours follow the app theme. The adaptive launcher icon is preserved.
51+
- **Architecture:** Navigation-Compose + ViewModel/StateFlow + Hilt, Retrofit + OkHttp + kotlinx.serialization, Coil for covers.
4852
All loading/empty/error states handled per screen.
4953

5054
## Internationalization (i18n)
5155

52-
The app is **fully localized in 4 languages — Italian, English, French, German** — matching the
53-
Pinakes backend locales. It follows the **device locale** by default and offers an **in-app language
56+
The app supports **4 languages — Italian, English, French, German**; the PHP server also supports Danish.
57+
It follows the **device locale** by default and offers an **in-app language
5458
switcher** in Profile (System default / Italiano / English / Français / Deutsch) via
5559
`AppCompatDelegate.setApplicationLocales(...)`, persisted across restarts (`autoStoreLocales`).
5660

57-
- **Single source of truth = JSON.** Translations live in `i18n/{en,it,fr,de}.json` (209 keys each,
61+
- **Single source of truth = JSON.** Translations live in `i18n/{en,it,fr,de}.json` (622 keys each,
5862
en = default/source). A Gradle task (`GenerateI18nResTask`) generates `res/values*/strings.xml` from
5963
those JSONs at build time, so the app uses standard Android string resources but the editable source
6064
stays JSON — syncable with the web app's `locale/*.json`.

‎_contract/MOBILE_API_SPEC.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
1111
## Goal
1212

13+
### Implemented extension: Mobile API 1.5.0 / Android 1.6.0
14+
15+
The coordinated changes in Pinakes #458 and Android #41 add capability-gated Archives and library Desiderata, separate from circulation inventory and personal wishlist. Authenticated collection routes provide independent cursors, public archive hierarchy/metadata/documents/exports, and verified-account donation proposals with consent. Proposal UUIDs survive retries and process death; the server deduplicates per account and exposes account-scoped outcome recovery.
16+
17+
Analytic article search supports shared author IDs, publisher, complete genre ancestry, language, container and keyword filters. Unsupported analytic filters require a server upgrade rather than returning unfiltered results. Book and article details expose complete bibliographic metadata, five server-generated citation styles and digital attachments; article RIS/MARCXML and protected staff-management links are retained. Old circulation routes stay compatible. See [the parity matrix](../docs/UWE-PARITY.md) and [release preparation](../docs/PLAY-RELEASES.md) for version dependencies and verified boundaries.
18+
19+
### Original API scope
20+
1321
Expose **everything a logged-in library user can do on the web** through a
1422
versioned REST API, so a native app can deliver: catalog search, book detail,
1523
loan/reservation requests, wishlist, profile, contact messaging, and push
@@ -31,7 +39,7 @@ stock). The app stores the instance URL + a long-lived per-device token.
3139
| Pagination | **Cursor-based** for catalog/lists: `meta.next_cursor` (opaque), `?cursor=...&limit=...`. |
3240
| Localization | Strings in the **instance locale** (decided at install); **dates ISO-8601 UTC**; the app formats locally. |
3341
| Write actions | reserve / request loan, **cancel reservation**, wishlist add/remove, **edit profile + change password**, **send contact message**. |
34-
| Search filters | text (title/author/keyword), author/publisher, **genre cascade (3 levels) + language**, **availability (loanable now)**. |
42+
| Search filters | text (title/author/keyword), author identity/publisher, **complete genre ancestry + language**, **availability (loanable now)**. |
3543
| Book detail | **Full** payload (availability, copies, shelf/location, absolute cover URL, full metadata, related) **+ personal history** (has the user read/reserved/wishlisted it). |
3644
| Caching | **ETag / Last-Modified** + cache headers on read endpoints; honor `If-None-Match` → 304. |
3745
| Push transport | **UnifiedPush** primary (the library manager self-registers a provider and creates the credentials — minimal setup). Behind a **pluggable `PushProvider` abstraction** (UnifiedPush impl now; FCM impl optional/stub). |

0 commit comments

Comments
 (0)