better UI, +windows1251 encoding support

This commit is contained in:
sergeych committed 2026-05-18 00:14:34 +03:00
1 parent ddf68c1e6a
commit da88a6e221
15 files changed
+407 -14

No files matched your search

@@ -166,6 +166,9 @@ private fun BookReaderApp(onThemeToggle: () -> Unit) {
message = current.message,
)
},
onDeleted = { message ->
state = AppState.Library(emptyList(), current.scanPath, message)
},
onBack = {
state = AppState.Library(emptyList(), current.scanPath, current.message)
},
@@ -0,0 +1,14 @@
package net.sergeych.toread
internal data class LibraryDeleteResult(
val deleted: Boolean,
val message: String,
)
internal suspend fun deleteLibraryBook(fileId: String, title: String): LibraryDeleteResult {
val deleted = runCatching { deleteLibraryItem(fileId) }.getOrDefault(false)
return LibraryDeleteResult(
deleted = deleted,
message = if (deleted) "Removed $title." else "Could not remove $title.",
)
}
@@ -108,6 +108,10 @@ expect suspend fun saveLibraryReadingPosition(fileId: String, position: ReadingP
expect suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingStatus): Boolean
expect suspend fun shareLibraryBookFile(fileId: String): Boolean
expect suspend fun viewLibraryBookFile(fileId: String): Boolean
expect suspend fun loadBookInfoExtras(fileId: String): BookInfoExtras
expect suspend fun loadActiveReadingFileId(): String?
@@ -364,9 +364,9 @@ internal fun LibraryScreen(
scope.launch {
busy = true
try {
val deleted = runCatching { deleteLibraryItem(item.fileId) }.getOrDefault(false)
message = if (deleted) "Removed ${item.title}." else "Could not remove ${item.title}."
if (deleted) {
val result = deleteLibraryBook(item.fileId, item.title)
message = result.message
if (result.deleted) {
items = items.filterNot { it.fileId == item.fileId }
searchResults = searchResults.filterNot { it.fileId == item.fileId }
coverCache.remove(item.fileId)
@@ -90,9 +90,9 @@ internal fun ContinuousBookReader(
val hyphenation = remember { HyphenationRegistry() }
val scope = rememberCoroutineScope()
val contentPadding = if (isAndroidPlatform()) {
PaddingValues(start = 6.dp, top = 6.dp, end = 0.dp, bottom = 6.dp)
PaddingValues(start = 0.dp, top = 6.dp, end = 0.dp, bottom = 6.dp)
} else {
PaddingValues(horizontal = 8.dp, vertical = 6.dp)
PaddingValues(horizontal = 4.dp, vertical = 6.dp)
}
LazyColumn(
@@ -461,8 +461,8 @@ private fun ReaderText(
@Composable
private fun readerParagraphTextStyle(language: String?): TextStyle =
MaterialTheme.typography.bodyLarge.copy(
fontWeight = FontWeight(350),
fontSize = 21.sp,
fontWeight = if( isAndroidPlatform()) FontWeight(350) else FontWeight.Normal,
fontSize = if( isAndroidPlatform()) 21.sp else 18.sp,
lineHeight = 28.sp,
hyphens = if (isAndroidPlatform()) Hyphens.Auto else Hyphens.Unspecified,
lineBreak = if (isAndroidPlatform()) LineBreak.Paragraph else LineBreak.Unspecified,
@@ -12,13 +12,19 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -47,13 +53,36 @@ internal fun BookView(
onImageOpen: (ViewedBookImage) -> Unit,
onThemeToggle: () -> Unit,
onBookInfo: () -> Unit,
onDeleted: (String) -> Unit,
onBack: () -> Unit,
) {
val stats = remember(book) { BookStats.from(book) }
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
var restored by remember(fileId) { mutableStateOf(false) }
var markedRead by remember(fileId) { mutableStateOf(false) }
val platformName = getPlatform().name
val showShareAction = platformName.startsWith("Android")
val showViewFileAction = platformName.startsWith("Java")
fun showMessage(message: String) {
scope.launch {
snackbarHostState.showSnackbar(message, duration = SnackbarDuration.Short)
}
}
fun setReadingStatus(status: BookReadingStatus, successMessage: String) {
scope.launch {
if (markLibraryReadingStatus(fileId, status)) {
if (status == BookReadingStatus.READ) markedRead = true
if (status == BookReadingStatus.NEW) markedRead = false
showMessage(successMessage)
} else {
showMessage("Could not update book.")
}
}
}
LaunchedEffect(fileId) {
markLibraryReadingStatus(fileId, BookReadingStatus.READING)
@@ -91,6 +120,7 @@ internal fun BookView(
Scaffold(
contentWindowInsets = WindowInsets(0, 0, 0, 0),
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
CompactReaderTopBar(
title = book.title,
@@ -104,6 +134,40 @@ internal fun BookView(
onBookInfo()
}
},
onMarkAsRead = {
setReadingStatus(BookReadingStatus.READ, "Marked as read.")
},
onNotInterested = {
setReadingStatus(BookReadingStatus.NOT_INTERESTED, "Marked as not interested.")
},
onClearMarks = {
setReadingStatus(BookReadingStatus.NEW, "Cleared marks.")
},
showShareAction = showShareAction,
onShare = {
scope.launch {
val shared = shareLibraryBookFile(fileId)
showMessage(if (shared) "Share opened." else "Could not share book.")
}
},
showViewFileAction = showViewFileAction,
onViewFile = {
scope.launch {
val opened = viewLibraryBookFile(fileId)
showMessage(if (opened) "Opened file location." else "Could not open file location.")
}
},
onDelete = {
scope.launch {
val result = deleteLibraryBook(fileId, book.title)
if (result.deleted) {
saveActiveReadingFileId(null)
onDeleted(result.message)
} else {
showMessage(result.message)
}
}
},
onBack = {
scope.launch {
saveLibraryReadingPosition(
@@ -139,8 +203,18 @@ private fun CompactReaderTopBar(
title: String,
onThemeToggle: () -> Unit,
onBookInfo: () -> Unit,
onMarkAsRead: () -> Unit,
onNotInterested: () -> Unit,
onClearMarks: () -> Unit,
showShareAction: Boolean,
onShare: () -> Unit,
showViewFileAction: Boolean,
onViewFile: () -> Unit,
onDelete: () -> Unit,
onBack: () -> Unit,
) {
var menuOpen by remember { mutableStateOf(false) }
ThemedTopBarSurface {
Row(
modifier = Modifier.fillMaxWidth().height(48.dp),
@@ -158,12 +232,74 @@ private fun CompactReaderTopBar(
IconButton(onClick = onThemeToggle) {
Icon(Icons.Filled.Palette, contentDescription = "Theme")
}
IconButton(onClick = onBookInfo) {
Icon(Icons.Filled.Info, contentDescription = "Properties")
}
IconButton(onClick = { }) {
Icon(Icons.AutoMirrored.Filled.VolumeUp, contentDescription = "Read aloud")
}
Box {
IconButton(onClick = { menuOpen = true }) {
Icon(Icons.Filled.MoreVert, contentDescription = "Book reader menu")
}
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
DropdownMenuItem(
text = { Text("Info...") },
onClick = {
menuOpen = false
onBookInfo()
},
)
HorizontalDivider()
DropdownMenuItem(
text = { Text("Mark as read") },
onClick = {
menuOpen = false
onMarkAsRead()
},
)
DropdownMenuItem(
text = { Text("Not interested") },
onClick = {
menuOpen = false
onNotInterested()
},
)
DropdownMenuItem(
text = { Text("Clear marks") },
onClick = {
menuOpen = false
onClearMarks()
},
)
if (showShareAction || showViewFileAction) {
HorizontalDivider()
}
if (showShareAction) {
DropdownMenuItem(
text = { Text("Share") },
onClick = {
menuOpen = false
onShare()
},
)
}
if (showViewFileAction) {
DropdownMenuItem(
text = { Text("View file") },
onClick = {
menuOpen = false
onViewFile()
},
)
}
HorizontalDivider()
DropdownMenuItem(
text = { Text("Delete") },
onClick = {
menuOpen = false
onDelete()
},
)
}
}
}
}
}