Add offline reading, bookmarks, three UI languages and downloads
Reading - Naskh is now the default face, and both Arabic-script fonts are registered at four weights so the text can be thickened for a lit screen. - Category listings show each poem's opening line, fetched best-effort from api.ganjoor.net since the data set doesn't carry excerpts. - Tap a couplet to save that passage or copy it; a saved passage keeps a tappable link back to its poem. Poem text is selectable for plain copying. Languages - Persian, Urdu and English, Persian by default whatever the phone's locale, applied in attachBaseContext and switchable from the top bar or the sheet. - English chrome uses Libron (OFL); poems stay naskh or nastaliq throughout. - Each language gets its own values-* folder: with Farsi only in values/, Android was resolving it to the Urdu strings, since a same-script locale outranks the default. Offline - Downloaded poets mirror the data set's layout under filesDir, so offline mode is one lookup rather than a parallel path. Downloads run one poet at a time, skip what's on disk, and resume by restarting. - Downloads screen lists every poet with their portrait and tick boxes for picking several, plus storage used, delete, and download-everything behind a size warning. - Offline mode refuses the network and says what's missing rather than blaming the connection. Also: poet sort (Ganjoor's order or alphabetical, via a Persian collator), RTL nav transitions, an original shamsa launcher icon with a monochrome layer for themed icons, and preferences written with commit so they survive the process being killed. F-Droid: dependenciesInfo off, optional signing so the build works with no keystore, fastlane metadata in three languages, cleartext traffic disabled. Verified on an API 36 emulator: downloaded a poet, pulled the emulator's network, read them offline, and confirmed an undownloaded poet reports being missing rather than erroring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d9915eac6c
commit
abcffbc43b
66 files changed
+1581
-328
No files matched your search
@@ -1,35 +1,61 @@
|
||||
# Ganjoor for Android
|
||||
|
||||
<div dir="rtl">گنجور — خوانندهٔ شعر پارسی برای اندروید</div>
|
||||
|
||||
An Android reader for [Ganjoor](https://ganjoor.net), the open archive of Persian poetry —
|
||||
240 poets and ~135,000 poems, laid out for comfortable long-form reading in Persian, Urdu
|
||||
and Arabic script.
|
||||
and Arabic script, online or fully offline.
|
||||
|
||||
Jetpack Compose, Material 3, `minSdk 24`.
|
||||
Jetpack Compose, Material 3, `minSdk 24`. No account, no tracking, no server of its own.
|
||||
|
||||
## What's here
|
||||
## Reading
|
||||
|
||||
**Reading.** Poems render as couplets: the two hemistichs of each line stack on a phone, the
|
||||
first aligned to the start of the line and the second to the end, the way Ganjoor itself reads.
|
||||
Prose sections (Golestan, for example) fill the column instead. The whole app lays out
|
||||
right-to-left.
|
||||
Poems are set as couplets: the two hemistichs of each line stack on a phone, the first aligned
|
||||
to the start of the line and the second to the end, the way Ganjoor itself reads. Prose sections
|
||||
— Golestan, Nowruznameh — fill the column instead. Category listings show each poem's opening
|
||||
line under its title, because "Ghazal 237" tells you nothing.
|
||||
|
||||
**Fonts.** Two bundled Noto families, switchable while reading:
|
||||
Tap a couplet to save that passage or copy it; a saved passage keeps a tappable link back to the
|
||||
poem it came from, which a plain copy would lose. The whole poem can be bookmarked from the top
|
||||
bar, and any span of text can be selected and copied the usual way.
|
||||
|
||||
| Font | File | Note |
|
||||
The whole interface lays out and navigates right-to-left, whichever UI language is chosen.
|
||||
|
||||
## Fonts
|
||||
|
||||
| Font | Used for | Licence |
|
||||
|---|---|---|
|
||||
| Naskh | `res/font/noto_naskh_arabic.ttf` | Covers Persian, Urdu, Arabic and Latin — also carries the UI |
|
||||
| Nastaliq | `res/font/noto_nastaliq_urdu.ttf` | Traditional hanging script, needs ~2.4× leading (see `readingStyle`) |
|
||||
| Noto Naskh Arabic | Poems, and the Persian/Urdu interface | OFL 1.1 |
|
||||
| Noto Nastaliq Urdu | Poems, when nastaliq is selected | OFL 1.1 |
|
||||
| [Libron](https://github.com/nicoverbruggen/libron) | The English interface only | OFL 1.1 |
|
||||
|
||||
Both are variable fonts shipped at their default weight; Android synthesises bold, since
|
||||
variable axes would need API 26+. Licences are in [`licenses/`](licenses/) (SIL OFL 1.1).
|
||||
Naskh is the default. Both Arabic-script faces are variable fonts registered at four weights, so
|
||||
the text can be thickened — thin naskh strokes wash out on a lit screen, especially in the dark
|
||||
themes. Real axis interpolation needs API 26+; below that Android synthesises the heavier
|
||||
weights. Libron is a reading serif and never touches the poems: content is always naskh or
|
||||
nastaliq, whatever language the interface is in.
|
||||
|
||||
**Themes.** Five modes, persisted across launches: System, Light, Dark, Sepia and Sepia night.
|
||||
The two sepia schemes are warm paper tones for long sessions — the dark one has no blue cast.
|
||||
Licences are in [`licenses/`](licenses/).
|
||||
|
||||
**Text size.** A slider from 14 to 40 sp with a live preview in the settings sheet.
|
||||
## Themes, size, language
|
||||
|
||||
**Summaries.** Ganjoor publishes AI-generated summaries for some poems and couplets. They are
|
||||
off by default and labelled as AI-generated wherever they appear.
|
||||
Five themes, persisted: System, Light, Dark, Sepia and Sepia night. The two sepia schemes are
|
||||
warm paper tones for long sessions; the dark one has no blue cast. Text size runs 14–40 sp with a
|
||||
live preview in the settings sheet.
|
||||
|
||||
The interface speaks **Persian, Urdu and English**, Persian by default regardless of the phone's
|
||||
locale. Switch from the globe in the top bar or from the settings sheet.
|
||||
|
||||
## Offline
|
||||
|
||||
Download a poet — or every poet — and read with no connection at all. Downloads are listed with
|
||||
each poet's portrait and tick boxes for picking several at once; the screen shows how much space
|
||||
they take and lets you delete any of them. Offline mode then refuses the network entirely and
|
||||
reads only what's on the device, saying so plainly when you open something that was never
|
||||
downloaded, rather than blaming your connection.
|
||||
|
||||
Downloads run one poet at a time and are resumable: anything already on disk is skipped, so
|
||||
restarting an interrupted download picks up where it left off.
|
||||
|
||||
## Where the poems come from
|
||||
|
||||
@@ -42,11 +68,13 @@ jsDelivr's CDN, and addressed by the poem's own Ganjoor URL:
|
||||
/hafez/ghazal -> poets/hafez/ghazal/_cat.json
|
||||
```
|
||||
|
||||
Because paths are URLs, the app never touches the numeric id indexes. Responses land in a 64 MB
|
||||
OkHttp disk cache, so anything already read stays readable offline.
|
||||
Because paths are URLs, the app never touches the numeric id indexes. Downloaded files mirror
|
||||
that same layout under `filesDir/offline`, which is why offline mode is a single lookup rather
|
||||
than a parallel code path. Everything else lands in a 64 MB OkHttp disk cache.
|
||||
|
||||
See [`data/Ganjoor.kt`](app/src/main/java/com/ganjoor/android/data/Ganjoor.kt) — the client is
|
||||
about thirty lines.
|
||||
One exception: opening lines aren't in the data set, so they're fetched best-effort from
|
||||
`api.ganjoor.net` and cached. Adding an `Excerpt` field to `_cat.json` upstream would remove
|
||||
that dependency — see the `ponytail:` note in `Ganjoor.kt`.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -55,42 +83,67 @@ about thirty lines.
|
||||
./gradlew :app:testDebugUnitTest # couplet grouping tests
|
||||
```
|
||||
|
||||
Release signing is optional. Drop a `keystore.properties` next to `settings.gradle.kts` with
|
||||
`storeFile`, `storePassword`, `keyAlias` and `keyPassword` to sign locally; without it the
|
||||
release build still succeeds, unsigned. The file and any `*.jks` are gitignored.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
data/Ganjoor.kt models, couplet grouping, the static-file client
|
||||
data/Offline.kt downloaded poems on disk
|
||||
data/Downloads.kt download queue and progress
|
||||
data/Bookmarks.kt saved poems and passages
|
||||
ui/GanjoorApp.kt nav graph; routes carry Ganjoor URLs
|
||||
ui/PoetsScreen.kt poet grid with name filter
|
||||
ui/PoetsScreen.kt poet grid, search, sort
|
||||
ui/CategoryScreen.kt collections and poem lists
|
||||
ui/PoemScreen.kt the reader
|
||||
ui/ReadingSettings.kt theme / font / size sheet
|
||||
ui/Settings.kt preferences
|
||||
ui/Load.kt fetch + loading + retry
|
||||
ui/DownloadsScreen.kt multi-select offline downloads
|
||||
ui/BookmarksScreen.kt saved poems and passages
|
||||
ui/ReadingSettings.kt theme / font / weight / size / language / offline
|
||||
ui/theme/ colour schemes and typography
|
||||
```
|
||||
|
||||
Single module, no DI framework, no ViewModels yet — screens fetch through `Load` and lean on the
|
||||
HTTP cache. `ponytail:` comments mark the deliberate shortcuts and what would replace them.
|
||||
cache. `ponytail:` comments mark the deliberate shortcuts and what would replace them.
|
||||
|
||||
## Not built yet
|
||||
|
||||
- **Search.** The data set has no search index; this needs either a client-side index or
|
||||
`api.ganjoor.net`'s `/api/ganjoor/poems/search`.
|
||||
- **Bookmarks and reading position.** Nothing is stored locally beyond preferences.
|
||||
- **Search within poems.** The data set has no search index; this needs either a client-side
|
||||
index or `api.ganjoor.net`'s `/api/ganjoor/poems/search`.
|
||||
- **Reading position.** Bookmarks are saved, but not where you stopped reading.
|
||||
- **Recitations.** Ganjoor has audio for many poems; it isn't in this data set.
|
||||
- **Offline download.** The HTTP cache covers what you've read, not a whole divan on demand.
|
||||
|
||||
## Publishing to F-Droid
|
||||
|
||||
The build already meets the [quick start
|
||||
guide](https://f-droid.org/en/docs/Submitting_to_F-Droid_Quick_Start_Guide/):
|
||||
|
||||
- MIT licensed, with a `LICENSE` file.
|
||||
- Every dependency is FOSS (AndroidX, Kotlin, OkHttp, Coil, Accompanist). No Play Services, no
|
||||
Firebase, no analytics, no trackers. Only the `INTERNET` permission, and cleartext disabled.
|
||||
- `versionCode`/`versionName` are literals in `app/build.gradle.kts`, not derived from git.
|
||||
- Dependency versions are all pinned; no version ranges.
|
||||
- `dependenciesInfo` is switched off — that blob is signed with a Google key and isn't
|
||||
reproducible, so F-Droid rejects APKs carrying it.
|
||||
- The build succeeds with no keystore, so F-Droid can sign with its own key.
|
||||
- Store listing lives in `fastlane/metadata/android/{en-US,fa,ur}/`. Drop screenshots into each
|
||||
locale's `images/phoneScreenshots/`.
|
||||
|
||||
Two things to do before submitting: tag a release (`v0.1.0`), and check that F-Droid's build
|
||||
server supports **AGP 9.4.1** — it is new, and that is the most likely thing to hold up a merge.
|
||||
|
||||
## Licensing
|
||||
|
||||
The app code is MIT (see [`LICENSE`](LICENSE)). Two things worth knowing about what it builds on:
|
||||
The app code is MIT (see [`LICENSE`](LICENSE)). What it builds on:
|
||||
|
||||
- **The poetry** is classical Persian verse, long out of copyright.
|
||||
- **The data set** ([`ganjoor/ganjoor-data`](https://github.com/ganjoor/ganjoor-data)) carries no
|
||||
licence file. Worth asking upstream to add an explicit one.
|
||||
licence file at all. Worth asking upstream to add an explicit one.
|
||||
- **[GanjoorService](https://github.com/ganjoor/GanjoorService)**, Ganjoor's own backend and site,
|
||||
is GPL-3.0. This app uses none of its code — only data over HTTPS — so it is not a derivative
|
||||
work of it.
|
||||
|
||||
Fonts are SIL OFL 1.1, which permits bundling in an application.
|
||||
work of it. Nothing in Ganjoor's repositories restricts AI-assisted use.
|
||||
- **The icon** is original: an eight-point shamsa, the star that tiles Persian architecture.
|
||||
Ganjoor's own app icons are unlicensed and are their mark, so they are not used here.
|
||||
|
||||
Not affiliated with or endorsed by Ganjoor.
|
||||
Reference in new issue
Block a user