Add poem search, an OLED theme, and credit every licence
Search: full-text across the poems, scoped to all poets, a few, or one. The static data set has no index, so this is ganjoor.net's own search endpoint, which scopes to one poet at a time — several picked poets run as several queries and merge. Entry is the poets filter box, which now sits in the bottom bar within thumb reach and offers its own words to the poem search one tap further. Snippets show the line the term is actually on. Themes: a sixth, true black, for OLED panels where an unlit pixel costs no power. Surfaces step up in near-blacks so cards stay distinguishable. Licences: every component is now credited in-app under Reading settings > About & licences, with the full text bundled in assets/licenses — the OFL requires the licence to travel with the software, so linking to it wasn't enough. licenses/README.md indexes the same thing for the repo. The credits list and the licence texts lay out left-to-right in English, since the rest of the app is right-to-left for the poetry's sake and English prose is unreadable right-aligned. Fixes a crash: the settings sheet was composed outside the provider supplying LocalOpenAbout, so opening it threw. Search decoding is covered by a unit test against a real API response; the rest verified on an API 36 emulator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1accb5ee78
commit
a5fc608bca
26 files changed
+1319
-27
No files matched your search
@@ -0,0 +1,64 @@
|
||||
package com.ganjoor.android
|
||||
|
||||
import com.ganjoor.android.data.SearchHit
|
||||
import com.ganjoor.android.data.snippet
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Trimmed from a real api.ganjoor.net response: the live endpoint returns ~30 fields per poem,
|
||||
* so the decoder has to ignore almost all of them.
|
||||
*/
|
||||
private const val RESPONSE = """
|
||||
[
|
||||
{
|
||||
"id": 2130,
|
||||
"title": "غزل شمارهٔ ۱",
|
||||
"fullTitle": "حافظ » غزلیات » غزل شمارهٔ ۱",
|
||||
"urlSlug": "sh1",
|
||||
"fullUrl": "/hafez/ghazal/sh1",
|
||||
"plainText": "الا یا ایها الساقی ادر کأسا و ناولها\nکه عشق آسان نمود اول ولی افتاد مشکل ها\nبه بوی نافه ای کآخر صبا زان طره بگشاید",
|
||||
"htmlText": "<div>…</div>",
|
||||
"sourceName": "ویکیدرج",
|
||||
"published": true,
|
||||
"coupletsCount": 7,
|
||||
"verses": [],
|
||||
"recitations": []
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
class SearchHitTest {
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
@Test
|
||||
fun `a live search response decodes despite its many unused fields`() {
|
||||
val hits = json.decodeFromString<List<SearchHit>>(RESPONSE)
|
||||
|
||||
assertEquals(1, hits.size)
|
||||
assertEquals("/hafez/ghazal/sh1", hits[0].fullUrl)
|
||||
assertEquals("حافظ » غزلیات » غزل شمارهٔ ۱", hits[0].fullTitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the snippet is the line the term is on, not the opening line`() {
|
||||
val hit = json.decodeFromString<List<SearchHit>>(RESPONSE).first()
|
||||
|
||||
assertTrue(snippet(hit, "نافه").contains("نافه"))
|
||||
assertTrue(snippet(hit, "عشق").startsWith("که عشق"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a term that isn't there falls back to the opening line`() {
|
||||
val hit = json.decodeFromString<List<SearchHit>>(RESPONSE).first()
|
||||
|
||||
assertEquals("الا یا ایها الساقی ادر کأسا و ناولها", snippet(hit, "زرافه"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a poem with no text yields an empty snippet rather than throwing`() {
|
||||
assertEquals("", snippet(SearchHit(id = 1), "عشق"))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user