Merge pull request #3 from anas-rashid/claude/design-system-extraction-c49naf

Fold the columns away while settings are open; couplets in rounded cards
This commit is contained in:
Anas Rashid 2026-10-07 18:13:20 +02:00 committed by GitHub
commit c3b22fdf0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 53 additions and 7 deletions

View File

@ -61,6 +61,7 @@ import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf
@ -156,6 +157,12 @@ private val CatEntry.url
is CatEntry.Poem -> poem.fullUrl
}
/**
* True while a side panel (the reading settings) is open beside the page on a large screen.
* The columns step aside for it, so the page keeps its room, and come back when it closes.
*/
val LocalSidePanelOpen = compositionLocalOf { false }
/**
* The columns beside [content]. [expanded] (840dp and up) shows up to three list columns;
* narrower, only the newest one, with a way back up in its header.
@ -174,7 +181,11 @@ fun ColumnBrowser(
content: @Composable (toggle: @Composable () -> Unit) -> Unit,
) {
val settings = LocalSettings.current
val hidden = settings.value.columnsHidden
// Hidden by the reader (reader view, remembered) or only while the settings panel is open.
// The second never touches the saved choice, so closing the panel puts back exactly what
// was there: the columns if they were showing, reader view if it was on.
val makingRoom = LocalSidePanelOpen.current
val hidden = settings.value.columnsHidden || makingRoom
val columns = remember(url, isPoem) { columnUrls(url, isPoem) }
// What is selected in each column: the next step of the path, down to the open poem.
val path = if (isPoem) columns + url else columns
@ -219,13 +230,14 @@ fun ColumnBrowser(
Box(Modifier.weight(1f).fillMaxHeight()) {
content {
// Hiding is in the top bar; bringing them back is the floating button below,
// which is there whenever they are hidden.
// which is there whenever the reader has hidden them.
if (!hidden) ColumnsToggle { settings.update { it.copy(columnsHidden = true) } }
}
// Reader view: the page has the whole screen, and one button, at the edge the
// columns went to, brings them back as they were.
// Not while the settings panel is open: the columns would stay away for it anyway.
ShowColumnsButton(
visible = hidden,
visible = hidden && !makingRoom,
onShow = { settings.update { it.copy(columnsHidden = false) } },
modifier = Modifier
.align(Alignment.BottomStart)

View File

@ -112,7 +112,10 @@ fun GanjoorApp() {
// On a large screen reading settings open in a panel on the left, where the dictionary
// opens, so the page stays in view and shows each change as it is made.
Row(Modifier.fillMaxSize()) {
// The columns fold away while the settings panel is open, so the page doesn't end up
// squeezed between them and the panel.
Box(Modifier.weight(1f)) {
CompositionLocalProvider(LocalSidePanelOpen provides (wide && settingsOpen)) {
NavHost(
navController = nav,
startDestination = PoetsRoute,
@ -237,6 +240,7 @@ fun GanjoorApp() {
}
}
}
}
if (wide) {
AnimatedVisibility(
visible = settingsOpen,

View File

@ -7,6 +7,10 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.clickable
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -251,6 +255,9 @@ fun PoemScreen(
}
}
/** The positions that make a line of verse; anything else (Single, Paragraph, Comment) is prose. */
private val VERSE_POSITIONS = setOf(Verse.RIGHT, Verse.LEFT, Verse.CENTERED_1, Verse.CENTERED_2)
/** A word someone tapped: what to look up, the couplet it came from, and where it sits in its verse. */
private data class WordTap(val word: String, val passage: Bookmark, val verse: Int, val range: IntRange)
@ -379,7 +386,26 @@ private fun Couplet(
val oneLine = sideBySide && couplet.size == 2 &&
couplet[0].position == Verse.RIGHT && couplet[1].position == Verse.LEFT
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
// Each line of verse sits in its own soft card, so the eye finds where one couplet ends and
// the next begins, and the couplet's actions visibly belong to it. Prose (Golestan,
// Nowruznameh) stays bare: a paragraph in a box reads as a quotation, not as the text.
val isVerse = couplet.all { it.position in VERSE_POSITIONS }
val colors = MaterialTheme.colorScheme
// A step lighter than the page. On OLED black the usual step is all but black itself, so
// the card takes the next one up: still dim, but there.
val cardColor =
if (colors.surface == Color.Black) colors.surfaceContainerHighest else colors.surfaceContainerHigh
val card = if (isVerse) {
Modifier
.padding(vertical = 4.dp)
.clip(RoundedCornerShape(12.dp))
.background(cardColor)
.padding(horizontal = 12.dp, vertical = 8.dp)
} else {
Modifier.padding(vertical = 6.dp)
}
Column(modifier = Modifier.fillMaxWidth().then(card)) {
if (oneLine) {
Row(modifier = Modifier.fillMaxWidth()) {
couplet.forEachIndexed { index, verse ->

View File

@ -1,6 +1,6 @@
# ColumnBrowser
The layout for tablets and unfolded foldables. A narrow column of poets sits on the right. Beside it is a column for each level of the open book (books › chapters › poems), and the open page fills the rest of the screen on the left. Phones keep today's navigation. Built in `app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt`; wireframes are in `design/wireframes/` (1–8).
The layout for tablets and unfolded foldables. A narrow column of poets sits on the right. Beside it is a column for each level of the open book (books › chapters › poems), and the open page fills the rest of the screen on the left. Phones keep today's navigation. Built in `app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt`; wireframes are in `design/wireframes/` (1–9).
## Window sizes
@ -51,6 +51,7 @@ On large screens, tapping a word opens **WordPanel** on the left instead of the
On large screens the gear opens **ReadingSettingsPanel** on the left, where the dictionary opens, instead of the bottom sheet:
- It has the same width and surface as the dictionary panel, with a "Reading settings" header and a ✕ button. The settings and their order are the same as in the sheet.
- The page stays in view beside the panel, so a change of theme, font, weight or size shows on the poem as it's made.
- While the panel is open, the poets and list columns fold away so the page keeps its room. When it closes they come back, but only if they were showing before: this never changes the saved reader-view choice, and the floating "show the list" button stays hidden while the panel is open.
- ✕ or Back closes it. Phones keep the bottom sheet.
## Loading

View File

@ -4,7 +4,9 @@ One line of poetry: two hemistichs stacked on a phone, the first pushed to the s
- **Consumer provides:** the couplet's verses (each with its position), the reading style from settings (`poem-naskh` or `poem-nastaliq` at the reader's size and weight), and handlers for a tapped word and for the actions.
- Alignment by verse position: first hemistich `start`, second `end`, centred verses `center`, prose (single/paragraph/comment) `justify` so Golestan and Nowruznameh fill the column.
- `space-6` above and below each couplet. Text is `on-surface`.
- Every line of verse sits in its own **card**: `surface-container-high` (`surface-container-highest` on OLED black, where the usual step is all but black), `radius-md` (12dp) corners, 12dp/8dp inner padding and 4dp above and below. The card holds the couplet's options chevron, its actions and its summary, so they visibly belong to it. Text is `on-surface`, at least 6:1 on the card in every theme.
- Prose (Single, Paragraph or Comment positions: Golestan, Nowruznameh) has no card; it keeps `space-6` above and below. A paragraph in a box would read as a quotation.
- The cards are the same on phones, tablets and foldables (wireframes 4–9).
- Tapping a word opens the dictionary sheet; tapping between words, or the 32px chevron (`touch-compact`, 20px icon, `on-surface-variant`), opens the couplet's actions: **Save this passage**, **Copy**, **Share** as TextButtons. Save keeps the link back to the poem; Copy doesn't.
- Long-press belongs to text selection — never bind actions to it here.
- With summaries on, Ganjoor's couplet summary sits underneath in `body-small` / `on-surface-variant`.

View File

@ -28,7 +28,8 @@
.gj-metre { font-size: 12px; line-height: 16px; letter-spacing: .4px; color: var(--on-surface-variant); }
/* Couplet: two hemistichs stacked; first to the start, second to the end. */
.gj-couplet { padding: var(--space-6) 0; }
.gj-couplet { margin: var(--space-4) 0; padding: var(--space-8) var(--space-12); border-radius: var(--radius-md); background: var(--surface-container-high); }
.gj-couplet.is-prose { margin: 0; padding: var(--space-6) 0; background: none; }
.gj-verse { font-family: var(--font-naskh); font-size: 22px; line-height: 1.8; color: var(--on-surface); margin: 0; }
.gj-couplet.is-nastaliq .gj-verse { font-family: var(--font-nastaliq); line-height: 2.4; }
.gj-verse.is-first { text-align: start; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB