better images and correct library re-sorting

This commit is contained in:
Sergey Chernov 2026-05-24 18:42:52 +03:00
parent 89b1115169
commit 17c885b3f5
4 changed files with 125 additions and 51 deletions

View File

@ -46,6 +46,11 @@ private data class PendingLibraryDelete(
val restore: () -> Unit,
)
internal data class LibraryItemRefreshRequest(
val id: Long,
val fileId: String,
)
@Composable
@Preview
fun App() {
@ -165,15 +170,23 @@ private fun BookReaderApp(
) -> Unit,
) {
var state by remember { mutableStateOf<AppState>(AppState.LoadingStartup) }
var libraryBackState by remember { mutableStateOf<AppState.Library?>(null) }
var activeScan by remember { mutableStateOf<LibraryScanProgress?>(null) }
var scanJob by remember { mutableStateOf<Job?>(null) }
var pendingDelete by remember { mutableStateOf<PendingLibraryDelete?>(null) }
var pendingDeleteJob by remember { mutableStateOf<Job?>(null) }
var hiddenDeletedFileIds by remember { mutableStateOf<Set<String>>(emptySet()) }
var nextDeleteId by remember { mutableStateOf(0L) }
var nextLibraryItemRefreshId by remember { mutableStateOf(0L) }
var libraryItemRefreshRequest by remember { mutableStateOf<LibraryItemRefreshRequest?>(null) }
var imageViewer by remember { mutableStateOf<ViewedBookImage?>(null) }
val scope = rememberCoroutineScope()
fun refreshLibraryItem(fileId: String) {
nextLibraryItemRefreshId += 1
libraryItemRefreshRequest = LibraryItemRefreshRequest(nextLibraryItemRefreshId, fileId)
}
LaunchedEffect(Unit) {
state = loadStartupState()
}
@ -279,7 +292,11 @@ private fun BookReaderApp(
)
is AppState.Reader -> {
scope.launch { saveActiveReadingFileId(null) }
AppState.Library(current.libraryItems, current.scanPath, current.message)
refreshLibraryItem(current.fileId)
val backState = libraryBackState?.copy(message = current.message)
?: AppState.Library(current.libraryItems, current.scanPath, current.message)
libraryBackState = null
backState
}
is AppState.Scan -> AppState.Library(current.items, current.scanPath, current.message)
is AppState.Error -> AppState.LoadingStartup
@ -340,17 +357,37 @@ private fun BookReaderApp(
}
}
when (val current = state) {
AppState.LoadingStartup -> LoadingScreen(strings.loadingOpeningBook)
is AppState.Library -> LibraryScreen(
state = current,
Box(Modifier.fillMaxSize()) {
val currentLibraryState = when (val current = state) {
is AppState.Library -> current
is AppState.Reader, is AppState.BookInfo -> libraryBackState
else -> null
}
currentLibraryState?.let { libraryState ->
LibraryScreen(
state = libraryState,
activeScan = activeScan,
itemRefreshRequest = libraryItemRefreshRequest,
hiddenFileIds = hiddenDeletedFileIds + pendingDelete?.request?.fileId?.let(::setOf).orEmpty(),
onStateChange = { state = it },
onNavigateToScan = { state = AppState.Scan(current.items, current.scanPath, current.message) },
onStateChange = { next ->
val currentState = state
if (next is AppState.Reader && currentState is AppState.Library) {
libraryBackState = currentState
}
state = next
},
onNavigateToScan = {
libraryBackState = null
state = AppState.Scan(libraryState.items, libraryState.scanPath, libraryState.message)
},
onStartScan = ::startScan,
onDeleteRequested = ::requestDelete,
)
}
when (val current = state) {
AppState.LoadingStartup -> LoadingScreen(strings.loadingOpeningBook)
is AppState.Library -> Unit
is AppState.Scan -> ScanScreen(
state = current,
activeScan = activeScan,
@ -365,6 +402,7 @@ private fun BookReaderApp(
book = current.book,
onImageOpen = { imageViewer = it },
onThemeToggle = onThemeToggle,
onBookChanged = ::refreshLibraryItem,
onBookInfo = {
state = AppState.BookInfo(
fileId = current.fileId,
@ -375,7 +413,9 @@ private fun BookReaderApp(
)
},
onDeleted = { message ->
state = AppState.Library(emptyList(), current.scanPath, message)
state = libraryBackState?.copy(message = message)
?: AppState.Library(emptyList(), current.scanPath, message)
libraryBackState = null
},
onDeleteRequested = ::requestDelete,
onBack = ::navigateBack,
@ -388,6 +428,7 @@ private fun BookReaderApp(
)
is AppState.Error -> ErrorScreen(current.message, onBack = ::navigateBack)
}
}
imageViewer?.let { image ->
ImageViewer(

View File

@ -82,6 +82,7 @@ import kotlinx.coroutines.launch
internal fun LibraryScreen(
state: AppState.Library,
activeScan: LibraryScanProgress?,
itemRefreshRequest: LibraryItemRefreshRequest?,
hiddenFileIds: Set<String>,
onStateChange: (AppState) -> Unit,
onNavigateToScan: () -> Unit,
@ -147,6 +148,13 @@ internal fun LibraryScreen(
recentlyAddedItems = loadRecentlyAddedLibraryItems(since, RecentlyAddedLimit)
}
suspend fun refreshLibraryItem(fileId: String) {
val updatedItem = loadLibraryItem(fileId) ?: return
items = items.replaceLibraryItem(updatedItem)
searchResults = searchResults.replaceLibraryItem(updatedItem)
recentlyAddedItems = recentlyAddedItems.replaceLibraryItem(updatedItem)
}
fun refresh(nextMessage: String? = message) {
message = nextMessage
scope.launch {
@ -247,6 +255,10 @@ internal fun LibraryScreen(
}
}
LaunchedEffect(itemRefreshRequest) {
itemRefreshRequest?.let { refreshLibraryItem(it.fileId) }
}
LaunchedEffect(searchText) {
val query = searchText
if (query.isBlank()) {

View File

@ -79,6 +79,7 @@ import androidx.compose.ui.text.style.LineBreak
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.sp
@ -96,7 +97,6 @@ import net.sergeych.toread.fb2.Fb2TextSpan
import net.sergeych.toread.fb2.Fb2TextStyle
import net.sergeych.toread.text.HyphenationRegistry
import net.sergeych.toread.text.SoftHyphen
import kotlin.math.min
import kotlinx.coroutines.launch
import kotlin.math.max
import kotlin.math.min
@ -117,7 +117,7 @@ internal fun ContinuousBookReader(
val hyphenation = remember { HyphenationRegistry() }
val scope = rememberCoroutineScope()
val textLineMetricsByItem = remember(contentPlan) { mutableStateMapOf<Int, TextLineMetrics>() }
val contentPadding = PaddingValues(6.dp)
val contentPadding = PaddingValues(top=6.dp, bottom = 6.dp, start = 0.dp, end = 6.dp)
val userScrollConnection = remember(onUserScroll) {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
@ -945,6 +945,13 @@ private val isDesktopPlatform: Boolean by lazy {
getPlatform().name.startsWith("Java")
}
private val MinimumBookImageMaxDimension = 800.dp
private fun minimumBookImageMaxDimension(availableWidth: Dp): Dp {
val relativeMinimum = availableWidth * 0.55f
return if (MinimumBookImageMaxDimension < relativeMinimum) MinimumBookImageMaxDimension else relativeMinimum
}
private fun TextLayoutResult.endsAtSoftHyphen(text: String, line: Int): Boolean {
val end = getLineEnd(line, visibleEnd = false)
return text.getOrNull(end - 1) == SoftHyphen || text.getOrNull(end) == SoftHyphen
@ -996,8 +1003,17 @@ private fun BookImage(
val imageAspectRatio = bitmap.width.toFloat() / bitmap.height.coerceAtLeast(1).toFloat()
val imageModifier = if (fitBackgroundToBitmapBounds) {
val bitmapWidth = with(density) { bitmap.width.toDp() }
val bitmapHeight = with(density) { bitmap.height.toDp() }
val bitmapMaxDimension = if (bitmapWidth > bitmapHeight) bitmapWidth else bitmapHeight
val minimumMaxDimension = minimumBookImageMaxDimension(maxWidth)
val displayWidth = when {
bitmapMaxDimension < minimumMaxDimension ->
bitmapWidth * (minimumMaxDimension.value / bitmapMaxDimension.value)
bitmapWidth < maxWidth -> bitmapWidth
else -> maxWidth
}
Modifier
.width(if (bitmapWidth < maxWidth) bitmapWidth else maxWidth)
.width(displayWidth)
.aspectRatio(imageAspectRatio)
} else {
Modifier.fillMaxSize()

View File

@ -66,6 +66,7 @@ internal fun BookView(
book: Fb2Book,
onImageOpen: (ViewedBookImage) -> Unit,
onThemeToggle: () -> Unit,
onBookChanged: (String) -> Unit,
onBookInfo: () -> Unit,
onDeleted: (String?) -> Unit,
onDeleteRequested: (
@ -110,6 +111,7 @@ internal fun BookView(
scope.launch {
if (markLibraryReadingStatus(fileId, status)) {
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = status)
onBookChanged(fileId)
if (status == BookReadingStatus.READ) markedRead = true
if (status == BookReadingStatus.NEW) markedRead = false
if (status == BookReadingStatus.NOT_INTERESTED) {
@ -134,6 +136,7 @@ internal fun BookView(
markedRead = item?.readingStatus == BookReadingStatus.READ
if (item?.readingStatus?.shouldBecomeReadingOnOpen() == true && markLibraryReadingStatus(fileId, BookReadingStatus.READING)) {
libraryItem = loadLibraryItem(fileId) ?: item.copy(readingStatus = BookReadingStatus.READING)
onBookChanged(fileId)
}
}
@ -182,6 +185,7 @@ internal fun BookView(
markedRead = true
if (markLibraryReadingStatus(fileId, BookReadingStatus.READ)) {
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = BookReadingStatus.READ)
onBookChanged(fileId)
}
}
}
@ -230,6 +234,7 @@ internal fun BookView(
scope.launch {
if (markLibraryFavorite(fileId, favorite)) {
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(favorite = favorite)
onBookChanged(fileId)
showMessage(if (favorite) strings.addedToFavorites() else strings.removedFromFavorites())
} else {
showMessage(strings.couldNotUpdateBook)