Native Android client for a Pinakes library instance. Browse the catalog, check real availability, borrow and reserve books, read ebooks and listen to audiobooks, and manage your loans, all from your phone. Optional Archives, Desiderata and analytic articles are available when the server advertises their APIs. The five bottom navigation destinations are retained.
Get the latest debug APK from the Releases page (pinakes-android-v<version>-debug.apk), then install it on your phone (enable install from unknown sources) or adb install -r <file>.apk. Point it at your Pinakes instance URL on first launch.
These are debug-signed builds for direct sideloading, not Play Store releases. Each release bumps
versionCode, so a newer APK installs over the previous one in place.
A single self-contained app any Pinakes library can hand to its members. On first
launch you enter your library's web address; the app discovers the instance
through its public /api/v1/health endpoint, you sign in with your library
account, and from then on everything talks to that instance's REST API over a
bearer token stored in EncryptedSharedPreferences.
The app is server-agnostic: it works against any Pinakes instance that has the Mobile API enabled, and it adapts to that instance's settings (language, catalogue-only mode, push availability).
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 for the component contract and API limitations.
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.
![]() Login |
![]() Home |
![]() Catalog + filters |
![]() Book detail |
![]() Availability calendar |
![]() My Library |
![]() Profile |
Admin and staff accounts get a native staff area when the instance runs Mobile API 1.7.0: cataloguing with ISBN scanning, the whole circulation desk and the content of the Desiderata, Archives, Periodicals, Book Club and FRBR plugins. Captured on Android 15 at 360 dp against a local instance with demo data.
![]() Circulation desk |
![]() Loans by status |
![]() Loan actions |
![]() Approve a request |
![]() New loan |
![]() Reservations queue |
![]() Scan a copy label |
![]() Admin area |
![]() Scan an ISBN |
![]() Books |
![]() Edit a record |
![]() Copies and labels |
![]() Author page |
![]() Authors |
![]() Genres |
![]() Series |
![]() Series detail |
![]() Merge duplicates |
![]() Import a file |
![]() Covers and enrichment |
![]() Donation offers |
![]() Archives |
![]() Periodicals desk |
![]() Staff tools |
| Area | What you get |
|---|---|
| Onboarding | Enter the instance URL, /health discovery card (library name, logo, HTTPS check, mobile-access check) |
| Sign in & sign up | Email/password login, in-app registration and password recovery, mapped error messages, secure token storage |
| Home | Searchable library hero, a fan of real shelf covers, and an "Available now" / recent shelf |
| Catalog | Two-column book grid or compact list, infinite scroll, search, sort and a filter sheet (availability, genre, author, publisher, language) |
| Book detail | HTML-rendered description, tap-to-zoom cover, full metadata block (ISBN, year, pages …), genre hierarchy |
| Availability | Neutral status pills with a coloured dot: green available, red on loan, amber reserved |
| Loan calendar | Pick a start date on a calendar that paints already-booked days and pre-selects the first free day |
| Audiobooks | In-app player (Media3 ExoPlayer) when the title has an audio file |
| Ebooks | In-app PDF reader (PdfRenderer); other formats open externally |
| My Library | Loans grouped into Overdue / On loan / Ready & scheduled / Pending, with due dates; reservations with cancel |
| Wishlist | Add and remove titles |
| 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 |
| Profile | Edit profile, change password, device list, theme switcher, language switcher, logout |
| Notifications | Loan due/overdue, reservation ready, book available |
| Themes | 2026 warm neutrals, Geist + Fraunces, complete 3D covers and paired theme colours. Light by default; pick light/dark/system in Profile |
| Admin area | For admin and staff accounts: cataloguing (ISBN/EAN scanner with batch mode, manual records, copies with their own codes, series, merge duplicates, label PDFs, CSV and LibraryThing import, covers and enrichment, authors, publishers, genres, shelves) and plugin content (donation offers, archives, periodicals desk, book clubs, FRBR works, digital files) |
| Circulation desk | Counters, loans by status, new loan with borrower search and copy scanning, approve/reject, pickup, return with copy outcome, renew, bulk extend, reminders, receipt PDF, reservations queue. Hidden while offline |
| Offline catalogue | Full catalogue mirrored on the device with delta sync; search, book pages, My Library and Wishlist work offline, while loan and reservation actions stay hidden until the connection is back |
| Loans in your calendar | A private, revocable calendar link with your loans (start to due date) and reservations, for Google Calendar, Apple Calendar, Outlook and others |
| Languages | Italian, English, French, German, following the device locale or an in-app choice |
- Kotlin 2.0 + Jetpack Compose (Material 3), single-module app
- Navigation-Compose +
ViewModel/StateFlow+ Hilt - Retrofit + OkHttp + kotlinx.serialization for the
/api/v1client ({data, meta, error}envelope) - Coil for cover images, Media3 ExoPlayer for audio, platform
PdfRendererfor PDFs - AndroidX Security (
EncryptedSharedPreferences) for the bearer token and instance URL - compileSdk/targetSdk 35, minSdk 26, JDK 21 toolchain, AGP 8.7, Gradle 8.10
- Pinned versions in
gradle/libs.versions.toml(no dynamic versions)
- JDK 21
- Android SDK with
platforms;android-35and matching build-tools - No Android Studio needed; the Gradle wrapper is included
./gradlew assembleDebug # debug APK → app/build/outputs/apk/debug/app-debug.apk
./gradlew lintDebug # static analysis
./gradlew testDebugUnitTest
adb shell pm revoke com.pinakes.app android.permission.CAMERA # the camera-denied test needs it denied
./gradlew connectedDebugAndroidTest # UI regression tests, with a running emulator
./gradlew assembleRelease # R8 + resource shrinking, unsigned without release credentialsCreate a local.properties with sdk.dir=/path/to/android-sdk (or set ANDROID_HOME).
Always build with the Gradle wrapper (./gradlew).
adb install -r app/build/outputs/apk/debug/app-debug.apkA prebuilt debug APK is published on the Releases page.
./tools/run-emulator.sh my_avd -no-snapshot-load -gpu autoThe launcher forwards the remaining options to the Android SDK emulator. On macOS,
it holds a caffeinate assertion for that emulator PID, then releases it when the
emulator exits; Ctrl+C stops both. This reduces host power throttling during local
testing. It does not change global power
settings or Android crash reporting.
An Android 15 startup ANR was reproduced before application initialization, along with system/launcher stalls. With the same debug APK, host priority dropped to 4 without the assertion and startup exceeded 21 seconds; with the assertion, three cold starts completed in 2.3–3.1 seconds. After restarting the VM through this launcher, five more cold starts completed in 1.66–1.80 seconds with no ANR events. An older AVD still stalled after a cold boot, and a restored snapshot retained a system-not-responding dialog. The assertion alone does not repair an unhealthy AVD; use a fresh development AVD without deleting needed user data. This is a development-emulator mitigation; device ANRs still require their own trace diagnosis.
On first launch the app asks for the instance URL.
- Production:
https://your-library.example(HTTPS required) - Local emulator to host dev server:
http://10.0.2.2:8081 - The instance must have mobile app access enabled, otherwise login is refused
Cleartext HTTP is only permitted for loopback and the emulator alias (localhost,
127.0.0.1, 10.0.2.2); every other host must be HTTPS.
The app talks to Pinakes' bundled Mobile API plugin (/api/v1). A library
admin enables it once:
- Admin → Plugins → Mobile API — activate the plugin. It is active by default on a fresh install; after an upgrade it ships disabled.
- Mobile API → Settings — turn on mobile app access (otherwise login is refused with "mobile app access is disabled on this library").
/api/v1/health and the interactive docs at /api/v1/docs are always reachable.
See the Mobile API documentation.
If the instance runs the Book Club plugin (with its mobile module enabled),
the app auto-discovers it via GET /api/v1/bookclub/health (a 2xx after login) and
adds a Book Club entry to the Profile tab; otherwise the section stays hidden.
It reuses the same bearer token — no extra login — and talks to the plugin's
/api/v1/bookclub/* surface. Advanced poll modes (stars / ranking / elimination)
and proposing a title open the corresponding web page.
Translations live in i18n/{en,it,fr,de}.json (one flat key/value map per
language; English is the source). A Gradle task generates the Android
res/values*/strings.xml from those JSON files at build time, so the editable
source stays in JSON and can be kept in sync with the Pinakes web locales. The
generated strings.xml files are git-ignored.
The app follows the device locale by default and offers an in-app language switcher in Profile (System / Italiano / English / Français / Deutsch).
If the instance runs in catalogue-only mode (/health reports
catalogue_mode: true and the matching features.* flags false), the app hides
loans, reservations and the wishlist: the Library and Wishlist tabs disappear and
the book detail drops its borrow/reserve actions, leaving a clean read-only
browsing experience.
app/src/main/java/com/pinakes/app/
├── data/
│ ├── model/ kotlinx.serialization models (mirror the live /api/v1 JSON)
│ ├── network/ PinakesApi (Retrofit) + interceptors
│ ├── repository/ Auth / Catalog / Library / Profile / Notifications
│ └── store/ SessionStore, FeatureStore, ThemeStore (EncryptedSharedPreferences)
├── di/ ServiceLocator
└── ui/
├── theme/ Color / Type / Theme (magenta #D70161 brand, light default)
├── components/ BookCard, AvailabilityChip, AudioPlayer, bottom bar …
├── navigation/ NavHost + scaffold
└── screens/ onboarding, login, home, search, detail, library, wishlist, profile, bookclub …
i18n/ en/it/fr/de.json (source of truth for strings)
_contract/ OpenAPI snapshot + API spec the app is built against
All core screens are implemented and verified end-to-end on an emulator against a
live instance (onboarding, login, catalog, detail, loans, reservations, audio, PDF,
themes, languages). UnifiedPush registration is wired in the data layer; the
distributor receiver is a follow-up. See STATUS.md for the full breakdown.
Released under the same license as Pinakes: AGPL-3.0.
On compatible servers, Emeroteca → Articles searches and displays standalone newspaper and magazine articles, without requiring ownership of their issues. Publication screens also link to their associated articles. Article covers, subtitles and published online resources follow the server data. Public PDFs use the URL supplied by the server; the website action opens the article’s full page. Older servers retain the existing periodicals browser. See the article integration notes.
The updated client reads Mobile API 1.5.0, Desiderata 1.2.0, Emeroteca 1.13.0 and Archives 1.5.1. Update the server/plugins before expecting the new optional collection screens. Older instances remain usable; unsupported article facets show an upgrade message rather than silently returning unfiltered results.
Desiderata is the library's wanted collection, separate from a member's wishlist. Donation contact details come from the verified account. Proposals require consent and survive transport retries/process death without creating inventory. Archives supports paged hierarchy, text/level/year search, authorities, all public documents and export links. Article/book citations are formatted by the server, with five styles and text/HTML clipboard copies. Every digital book attachment is shown; audio switches one native player at a time.
Administrative cataloguing and uploads open the protected PHP website, including the existing article form. No native administrative CRUD API is invented.






























