Compare commits
1 Commits
main
...
feature/la
| Author | SHA1 | Date | |
|---|---|---|---|
| e284872eaa |
@ -4,14 +4,19 @@ import androidx.activity.compose.BackHandler
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
@ -20,10 +25,12 @@ import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.FavoriteBorder
|
import androidx.compose.material.icons.filled.FavoriteBorder
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
@ -38,6 +45,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
@ -115,17 +123,99 @@ fun PoemScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { insets ->
|
) { insets ->
|
||||||
// Free-form selection for copying any span; tapping a word looks it up, and the
|
// Narrow and wide are the same reader in different containers. On a phone the poem
|
||||||
// per-couplet actions save a passage with the reference attached, which a raw copy
|
// has the window to itself and the dictionary arrives as a sheet over it; given the
|
||||||
// would lose.
|
// width of a tablet or an opened foldable, the poem's place in the poet's work sits
|
||||||
|
// on one side of it and the dictionary on the other, and nothing covers the verse.
|
||||||
|
BoxWithConstraints(Modifier.fillMaxSize()) {
|
||||||
|
val wide = maxWidth >= WideScreen
|
||||||
|
|
||||||
|
val body: @Composable (Modifier) -> Unit = { modifier ->
|
||||||
|
// Free-form selection for copying any span; tapping a word looks it up, and
|
||||||
|
// the per-couplet actions save a passage with the reference attached, which a
|
||||||
|
// raw copy would lose.
|
||||||
//
|
//
|
||||||
// The system selection menu gets its own entries from ProcessTextActivity, not from
|
// The system selection menu gets its own entries from ProcessTextActivity,
|
||||||
// here: Compose 1.10 stopped routing SelectionContainer through LocalTextToolbar, so
|
// not from here: Compose 1.10 stopped routing SelectionContainer through
|
||||||
// a custom TextToolbar is never asked to show. ACTION_PROCESS_TEXT goes round that,
|
// LocalTextToolbar, so a custom TextToolbar is never asked to show.
|
||||||
// and reaches every other app's selection menu as a side effect.
|
// ACTION_PROCESS_TEXT goes round that, and reaches every other app's
|
||||||
SelectionContainer {
|
// selection menu as a side effect.
|
||||||
|
SelectionContainer(modifier) {
|
||||||
|
PoemBody(
|
||||||
|
poem = poem,
|
||||||
|
fullUrl = fullUrl,
|
||||||
|
couplets = couplets,
|
||||||
|
style = style,
|
||||||
|
showSummaries = prefs.showSummaries,
|
||||||
|
siblings = siblings,
|
||||||
|
here = here,
|
||||||
|
insets = insets,
|
||||||
|
onPoem = onPoem,
|
||||||
|
onCategory = onCategory,
|
||||||
|
onWord = { word, passage -> tapped = word to passage },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wide) {
|
||||||
|
Row(Modifier.fillMaxSize()) {
|
||||||
|
// First child, so in this right-to-left layout it lands on the right.
|
||||||
|
PoemTree(
|
||||||
|
poemUrl = fullUrl,
|
||||||
|
onPoem = onPoem,
|
||||||
|
contentPadding = insets,
|
||||||
|
modifier = Modifier.width(300.dp).fillMaxHeight(),
|
||||||
|
)
|
||||||
|
VerticalDivider()
|
||||||
|
body(Modifier.weight(1f))
|
||||||
|
tapped?.let { (word, passage) ->
|
||||||
|
VerticalDivider()
|
||||||
|
DictionaryPane(
|
||||||
|
word = word,
|
||||||
|
passage = passage,
|
||||||
|
onClose = { tapped = null },
|
||||||
|
insets = insets,
|
||||||
|
modifier = Modifier.width(360.dp).fillMaxHeight(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body(Modifier)
|
||||||
|
tapped?.let { (word, passage) ->
|
||||||
|
WordSheet(
|
||||||
|
word = word,
|
||||||
|
passage = passage,
|
||||||
|
onDismiss = { tapped = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The poem itself, held to a reading measure. Left to fill a tablet, a couplet's two hemistichs
|
||||||
|
* are flung to opposite edges of the window with a hand's width of nothing between them, and the
|
||||||
|
* eye has to cross the whole screen between halves of one line.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun PoemBody(
|
||||||
|
poem: Poem,
|
||||||
|
fullUrl: String,
|
||||||
|
couplets: List<List<Verse>>,
|
||||||
|
style: androidx.compose.ui.text.TextStyle,
|
||||||
|
showSummaries: Boolean,
|
||||||
|
siblings: List<PoemRef>,
|
||||||
|
here: Int,
|
||||||
|
insets: PaddingValues,
|
||||||
|
onPoem: (String) -> Unit,
|
||||||
|
onCategory: (String) -> Unit,
|
||||||
|
onWord: (String, Bookmark) -> Unit,
|
||||||
|
) {
|
||||||
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.widthIn(max = ReadingMeasure).fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
start = 20.dp,
|
start = 20.dp,
|
||||||
end = 20.dp,
|
end = 20.dp,
|
||||||
@ -151,13 +241,13 @@ fun PoemScreen(
|
|||||||
Couplet(
|
Couplet(
|
||||||
couplet = couplet,
|
couplet = couplet,
|
||||||
style = style,
|
style = style,
|
||||||
showSummaries = prefs.showSummaries,
|
showSummaries = showSummaries,
|
||||||
source = Bookmark(fullUrl, poem.title, poem.fullTitle),
|
source = Bookmark(fullUrl, poem.title, poem.fullTitle),
|
||||||
onWord = { word, passage -> tapped = word to passage },
|
onWord = onWord,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.showSummaries) {
|
if (showSummaries) {
|
||||||
poem.poemSummary?.takeIf { it.isNotBlank() }?.let { summary ->
|
poem.poemSummary?.takeIf { it.isNotBlank() }?.let { summary ->
|
||||||
item { PoemSummary(summary) }
|
item { PoemSummary(summary) }
|
||||||
}
|
}
|
||||||
@ -184,14 +274,37 @@ fun PoemScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tapped?.let { (word, passage) ->
|
/**
|
||||||
WordSheet(
|
* The dictionary as a column beside the poem rather than a sheet over it, so the line the word
|
||||||
word = word,
|
* came from stays in view while its meaning is read. Closeable, because it holds its word until
|
||||||
passage = passage,
|
* dismissed — unlike the sheet, which a tap outside takes away.
|
||||||
onDismiss = { tapped = null },
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun DictionaryPane(
|
||||||
|
word: String,
|
||||||
|
passage: Bookmark,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
insets: PaddingValues,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(modifier.padding(top = insets.calculateTopPadding())) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(start = 20.dp, end = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.dictionary),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
|
IconButton(onClick = onClose) {
|
||||||
|
Icon(Icons.Default.Close, stringResource(R.string.close))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalDivider()
|
||||||
|
WordLookup(word) { PassageFooter(passage) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
226
app/src/main/java/com/ganjoor/android/ui/PoemTree.kt
Normal file
226
app/src/main/java/com/ganjoor/android/ui/PoemTree.kt
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package com.ganjoor.android.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.ganjoor.android.R
|
||||||
|
import com.ganjoor.android.data.Category
|
||||||
|
import com.ganjoor.android.data.Ganjoor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Below this, the window is a phone: one column, and the dictionary arrives as a sheet over the
|
||||||
|
* poem. At or above it there is room to put the poem's place in the poet's work beside it, and
|
||||||
|
* the dictionary beside that, without squeezing the measure the poetry is set to.
|
||||||
|
*
|
||||||
|
* 840dp is Material's "expanded" width — a tablet in landscape, or a foldable opened out.
|
||||||
|
*/
|
||||||
|
val WideScreen = 840.dp
|
||||||
|
|
||||||
|
/** The reading measure. A line of verse set across a whole tablet is a line nobody can follow. */
|
||||||
|
val ReadingMeasure = 680.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One row of the contents tree. [expanded] is null for a poem, which has nothing to open;
|
||||||
|
* [url] is null for the placeholder shown while a branch is still loading.
|
||||||
|
*/
|
||||||
|
private data class TreeRow(
|
||||||
|
val label: String,
|
||||||
|
val url: String?,
|
||||||
|
val depth: Int,
|
||||||
|
val expanded: Boolean?,
|
||||||
|
val current: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Every category above [poemUrl]: `/hafez/ghazal/sh1` -> `/hafez`, `/hafez/ghazal`. */
|
||||||
|
private fun ancestorUrls(poemUrl: String): List<String> {
|
||||||
|
val segments = poemUrl.trim('/').split('/').filter { it.isNotEmpty() }
|
||||||
|
if (segments.size <= 1) return emptyList()
|
||||||
|
return (1 until segments.size).map { "/" + segments.take(it).joinToString("/") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens the loaded part of the tree into rows. A branch that is open but not yet fetched
|
||||||
|
* contributes a spinner rather than nothing, so an expanding node never looks broken.
|
||||||
|
*/
|
||||||
|
private fun buildRows(
|
||||||
|
root: String,
|
||||||
|
loaded: Map<String, Category>,
|
||||||
|
expanded: Set<String>,
|
||||||
|
current: String,
|
||||||
|
): List<TreeRow> {
|
||||||
|
val rows = mutableListOf<TreeRow>()
|
||||||
|
|
||||||
|
fun walk(url: String, depth: Int) {
|
||||||
|
val category = loaded[url]
|
||||||
|
if (category == null) {
|
||||||
|
rows += TreeRow(label = "", url = null, depth = depth, expanded = null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
category.childCats.forEach { child ->
|
||||||
|
val open = child.fullUrl in expanded
|
||||||
|
rows += TreeRow(child.title, child.fullUrl, depth, open)
|
||||||
|
if (open) walk(child.fullUrl, depth + 1)
|
||||||
|
}
|
||||||
|
category.poems.forEach { poem ->
|
||||||
|
rows += TreeRow(poem.title, poem.fullUrl, depth, null, poem.fullUrl == current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(root, 0)
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where this poem sits in its poet's work — poet » book » section » poem — as a tree beside the
|
||||||
|
* reader, with the path to the open poem already unfolded and scrolled to.
|
||||||
|
*
|
||||||
|
* Tapping a category folds it open or shut rather than navigating: a section's own page lists the
|
||||||
|
* same poems this tree already shows, so opening it would only cost the reader their place. The
|
||||||
|
* breadcrumbs above the poem are the way out to a category page.
|
||||||
|
*
|
||||||
|
* ponytail: branches are fetched as they open and kept only while the screen lives. The disk
|
||||||
|
* cache makes re-opening cheap, and a poet's whole tree is far too much to hold eagerly.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun PoemTree(
|
||||||
|
poemUrl: String,
|
||||||
|
onPoem: (String) -> Unit,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val ancestors = remember(poemUrl) { ancestorUrls(poemUrl) }
|
||||||
|
if (ancestors.isEmpty()) return
|
||||||
|
val root = ancestors.first()
|
||||||
|
|
||||||
|
val loaded = remember(poemUrl) { mutableStateMapOf<String, Category>() }
|
||||||
|
var expanded by remember(poemUrl) { mutableStateOf(ancestors.toSet()) }
|
||||||
|
var unreachable by remember(poemUrl) { mutableStateOf(emptySet<String>()) }
|
||||||
|
|
||||||
|
LaunchedEffect(poemUrl, expanded) {
|
||||||
|
expanded.filterNot { it in loaded || it in unreachable }.forEach { url ->
|
||||||
|
runCatching { Ganjoor.category(url) }
|
||||||
|
.onSuccess { loaded[url] = it }
|
||||||
|
// A branch that will not load stays shut rather than retrying on every
|
||||||
|
// recomposition; the poem itself is already on screen and still readable.
|
||||||
|
.onFailure { unreachable = unreachable + url }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val rows = remember(loaded.size, expanded, poemUrl) {
|
||||||
|
buildRows(root, loaded, expanded, poemUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
var settled by remember(poemUrl) { mutableStateOf(false) }
|
||||||
|
// Once, when the open poem's row first exists: a book of 495 ghazals opens far from the one
|
||||||
|
// being read, and a tree that starts at ghazal 1 is a tree nobody scrolls.
|
||||||
|
LaunchedEffect(rows) {
|
||||||
|
if (settled) return@LaunchedEffect
|
||||||
|
val index = rows.indexOfFirst { it.current }
|
||||||
|
if (index >= 0) {
|
||||||
|
listState.scrollToItem(index + 1)
|
||||||
|
settled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(modifier = modifier, state = listState, contentPadding = contentPadding) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = loaded[root]?.title ?: stringResource(R.string.contents),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 12.dp, bottom = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
items(rows) { row ->
|
||||||
|
TreeRowItem(
|
||||||
|
row = row,
|
||||||
|
onToggle = {
|
||||||
|
expanded = if (row.url in expanded) expanded - row.url!! else expanded + row.url!!
|
||||||
|
},
|
||||||
|
onPoem = onPoem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TreeRowItem(row: TreeRow, onToggle: () -> Unit, onPoem: (String) -> Unit) {
|
||||||
|
// Indent by depth, on top of the row's own padding, so nesting reads at a glance.
|
||||||
|
val indent = (row.depth * 14).dp
|
||||||
|
|
||||||
|
if (row.url == null) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(start = indent + 12.dp, top = 8.dp, bottom = 8.dp),
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCategory = row.expanded != null
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { if (isCategory) onToggle() else onPoem(row.url) }
|
||||||
|
.padding(start = indent + 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
if (isCategory) {
|
||||||
|
Icon(
|
||||||
|
// Down when open, and pointing along the text when shut — the chevron is
|
||||||
|
// auto-mirrored, so it turns the right way in a right-to-left layout.
|
||||||
|
imageVector = if (row.expanded == true) Icons.Default.KeyboardArrowDown
|
||||||
|
else Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
|
contentDescription = stringResource(
|
||||||
|
if (row.expanded == true) R.string.collapse else R.string.expand
|
||||||
|
),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Text(
|
||||||
|
text = row.label,
|
||||||
|
style = if (isCategory) MaterialTheme.typography.bodyMedium
|
||||||
|
else MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = if (row.current) FontWeight.Bold else null,
|
||||||
|
color = when {
|
||||||
|
row.current -> MaterialTheme.colorScheme.primary
|
||||||
|
isCategory -> MaterialTheme.colorScheme.onSurface
|
||||||
|
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -78,8 +78,19 @@ fun WordSheet(word: String, onDismiss: () -> Unit, passage: Bookmark? = null) {
|
|||||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||||
// Inside the lookup's own scroll, not after it: the lookup scrolls, so anything placed
|
// Inside the lookup's own scroll, not after it: the lookup scrolls, so anything placed
|
||||||
// below it is pushed past the bottom of the sheet with no way to reach it.
|
// below it is pushed past the bottom of the sheet with no way to reach it.
|
||||||
WordLookup(word) {
|
WordLookup(word) { PassageFooter(passage) }
|
||||||
if (passage == null) return@WordLookup
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The couplet the word was tapped in, with its own actions, under the definitions.
|
||||||
|
*
|
||||||
|
* Shared by the phone's sheet and the wide-screen dictionary pane, which show the same lookup in
|
||||||
|
* two different containers.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun ColumnScope.PassageFooter(passage: Bookmark?) {
|
||||||
|
if (passage == null) return
|
||||||
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
|
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.this_couplet),
|
text = stringResource(R.string.this_couplet),
|
||||||
@ -96,8 +107,6 @@ fun WordSheet(word: String, onDismiss: () -> Unit, passage: Bookmark? = null) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The lookup itself. Split out from the sheet because the selection-menu activity shows the same
|
* The lookup itself. Split out from the sheet because the selection-menu activity shows the same
|
||||||
|
|||||||
@ -115,4 +115,8 @@
|
|||||||
<string name="view_grid">نمایش کارتی</string>
|
<string name="view_grid">نمایش کارتی</string>
|
||||||
<string name="view_list">نمایش فهرستی</string>
|
<string name="view_list">نمایش فهرستی</string>
|
||||||
<string name="downloaded_poet">پیشتر دریافت شده</string>
|
<string name="downloaded_poet">پیشتر دریافت شده</string>
|
||||||
|
<string name="close">بستن</string>
|
||||||
|
<string name="contents">فهرست</string>
|
||||||
|
<string name="expand">گشودن</string>
|
||||||
|
<string name="collapse">بستن شاخه</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -115,4 +115,8 @@
|
|||||||
<string name="view_grid">کارڈ کی صورت</string>
|
<string name="view_grid">کارڈ کی صورت</string>
|
||||||
<string name="view_list">فہرست کی صورت</string>
|
<string name="view_list">فہرست کی صورت</string>
|
||||||
<string name="downloaded_poet">پہلے سے محفوظ ہے</string>
|
<string name="downloaded_poet">پہلے سے محفوظ ہے</string>
|
||||||
|
<string name="close">بند کریں</string>
|
||||||
|
<string name="contents">فہرست</string>
|
||||||
|
<string name="expand">کھولیں</string>
|
||||||
|
<string name="collapse">بند کریں</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -120,4 +120,8 @@
|
|||||||
<string name="view_grid">Show as cards</string>
|
<string name="view_grid">Show as cards</string>
|
||||||
<string name="view_list">Show as a list</string>
|
<string name="view_list">Show as a list</string>
|
||||||
<string name="downloaded_poet">Already downloaded</string>
|
<string name="downloaded_poet">Already downloaded</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
|
<string name="contents">Contents</string>
|
||||||
|
<string name="expand">Expand</string>
|
||||||
|
<string name="collapse">Collapse</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user