diff --git a/app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt b/app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt index cd98363..f9d3c48 100644 --- a/app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt +++ b/app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt @@ -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) diff --git a/app/src/main/java/com/ganjoor/android/ui/GanjoorApp.kt b/app/src/main/java/com/ganjoor/android/ui/GanjoorApp.kt index 786ded0..b8cf5b6 100644 --- a/app/src/main/java/com/ganjoor/android/ui/GanjoorApp.kt +++ b/app/src/main/java/com/ganjoor/android/ui/GanjoorApp.kt @@ -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, diff --git a/app/src/main/java/com/ganjoor/android/ui/PoemScreen.kt b/app/src/main/java/com/ganjoor/android/ui/PoemScreen.kt index 11a3d47..82a1ba2 100644 --- a/app/src/main/java/com/ganjoor/android/ui/PoemScreen.kt +++ b/app/src/main/java/com/ganjoor/android/ui/PoemScreen.kt @@ -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 -> diff --git a/design/components/ColumnBrowser/README.md b/design/components/ColumnBrowser/README.md index b249451..3498562 100644 --- a/design/components/ColumnBrowser/README.md +++ b/design/components/ColumnBrowser/README.md @@ -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 diff --git a/design/components/Couplet/README.md b/design/components/Couplet/README.md index bc584d3..821963b 100644 --- a/design/components/Couplet/README.md +++ b/design/components/Couplet/README.md @@ -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`. diff --git a/design/components/bundle.css b/design/components/bundle.css index 688dd83..90c1124 100644 --- a/design/components/bundle.css +++ b/design/components/bundle.css @@ -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; } diff --git a/design/wireframes/4-reading.png b/design/wireframes/4-reading.png index aebcf6d..7d1b024 100644 Binary files a/design/wireframes/4-reading.png and b/design/wireframes/4-reading.png differ diff --git a/design/wireframes/5-reader-view.png b/design/wireframes/5-reader-view.png index 4ede9e1..922b625 100644 Binary files a/design/wireframes/5-reader-view.png and b/design/wireframes/5-reader-view.png differ diff --git a/design/wireframes/6-dictionary.png b/design/wireframes/6-dictionary.png index 82642cc..d0994ea 100644 Binary files a/design/wireframes/6-dictionary.png and b/design/wireframes/6-dictionary.png differ diff --git a/design/wireframes/7-foldable.png b/design/wireframes/7-foldable.png index 7d789ae..35a7566 100644 Binary files a/design/wireframes/7-foldable.png and b/design/wireframes/7-foldable.png differ diff --git a/design/wireframes/8-settings.png b/design/wireframes/8-settings.png index c018dcb..068b1fd 100644 Binary files a/design/wireframes/8-settings.png and b/design/wireframes/8-settings.png differ diff --git a/design/wireframes/9-phone-couplets.png b/design/wireframes/9-phone-couplets.png new file mode 100644 index 0000000..a901552 Binary files /dev/null and b/design/wireframes/9-phone-couplets.png differ