Navigate the poem tree instead of the visit history

Breadcrumbs: a poem's path (poet » book » section) is now built from its own
fullTitle and fullUrl and every ancestor is tappable, so the trail is the same
whether the poem was opened by browsing, from the saved list, from a
breadcrumb, or by reading on from the previous poem.

Back: browsing is a tree, so Back climbs it — poem to section to book to poet
to home — rather than retracing however you arrived. Each screen derives its
own parent from its URL and navigation keeps the stack flat, which is what
stops Back from walking forward again into the page you just left. System Back
and the top-bar arrow do the same thing.

The exception is a poem opened from the saved list: that was reached from a
list, not a shelf, so Back returns to the list, and reading on through the
divan keeps that origin.

Plus a Home button on every screen below the poet list.

Verified on an API 36 emulator: drilled Hafez > ghazals > ghazal 3 and walked
back up to the poet list, and opened a saved Khayyam rubai and confirmed Back
returns to the saved list rather than the poet's section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anas RashidandClaude Opus 5 committed 2026-10-04 00:28:52 +02:00
1 parent abcffbc43b
commit 1accb5ee78
11 files changed
+222 -27

No files matched your search

@@ -1,6 +1,9 @@
package com.ganjoor.android
import com.ganjoor.android.data.Crumb
import com.ganjoor.android.data.Verse
import com.ganjoor.android.data.breadcrumbs
import com.ganjoor.android.data.parentUrl
import com.ganjoor.android.data.couplets
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -66,3 +69,59 @@ class CoupletsTest {
assertEquals(listOf(2, 1, 1), grouped.map { it.size })
}
}
class BreadcrumbsTest {
@Test
fun `every ancestor is linked and the poem itself is not`() {
val crumbs = breadcrumbs("حافظ » غزلیات » غزل شمارهٔ ۱", "/hafez/ghazal/sh1")
assertEquals(listOf("حافظ", "غزلیات", "غزل شمارهٔ ۱"), crumbs.map { it.label })
assertEquals(listOf("/hafez", "/hafez/ghazal", null), crumbs.map { it.url })
}
@Test
fun `deeply nested books keep the whole trail`() {
val crumbs = breadcrumbs(
"مولانا » مثنوی معنوی » دفتر اول » بخش ۱",
"/moulavi/masnavi/daftar1/sh1",
)
assertEquals(4, crumbs.size)
assertEquals("/moulavi/masnavi/daftar1", crumbs[2].url)
assertEquals(null, crumbs.last().url)
}
@Test
fun `a title and url that disagree produce nothing, so the caller falls back`() {
assertEquals(emptyList<Crumb>(), breadcrumbs("حافظ » غزل ۱", "/hafez/ghazal/sh1"))
assertEquals(emptyList<Crumb>(), breadcrumbs("", "/hafez/ghazal/sh1"))
}
@Test
fun `a poem directly under a poet still links the poet`() {
val crumbs = breadcrumbs("حافظ » ساقی‌نامه", "/hafez/saghinameh")
assertEquals(listOf("/hafez", null), crumbs.map { it.url })
}
}
class ParentUrlTest {
@Test
fun `each level climbs to the one above it`() {
assertEquals("/hafez/ghazal", parentUrl("/hafez/ghazal/sh1"))
assertEquals("/hafez", parentUrl("/hafez/ghazal"))
assertEquals("/moulavi/masnavi", parentUrl("/moulavi/masnavi/daftar1"))
}
@Test
fun `a poet root has no parent, so the caller sends you home`() {
assertEquals(null, parentUrl("/hafez"))
assertEquals(null, parentUrl("hafez"))
assertEquals(null, parentUrl(""))
}
@Test
fun `trailing slashes don't invent a level`() {
assertEquals("/hafez", parentUrl("/hafez/ghazal/"))
}
}