better images and correct library re-sorting
This commit is contained in:
parent
89b1115169
commit
17c885b3f5
@ -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,53 +357,77 @@ private fun BookReaderApp(
|
||||
}
|
||||
}
|
||||
|
||||
when (val current = state) {
|
||||
AppState.LoadingStartup -> LoadingScreen(strings.loadingOpeningBook)
|
||||
is AppState.Library -> LibraryScreen(
|
||||
state = current,
|
||||
activeScan = activeScan,
|
||||
hiddenFileIds = hiddenDeletedFileIds + pendingDelete?.request?.fileId?.let(::setOf).orEmpty(),
|
||||
onStateChange = { state = it },
|
||||
onNavigateToScan = { state = AppState.Scan(current.items, current.scanPath, current.message) },
|
||||
onStartScan = ::startScan,
|
||||
onDeleteRequested = ::requestDelete,
|
||||
)
|
||||
is AppState.Scan -> ScanScreen(
|
||||
state = current,
|
||||
activeScan = activeScan,
|
||||
onStateChange = { state = it },
|
||||
onStartScan = { path ->
|
||||
startScan(path)
|
||||
state = AppState.Library(current.items, path, strings.scanning)
|
||||
},
|
||||
)
|
||||
is AppState.Reader -> BookView(
|
||||
fileId = current.fileId,
|
||||
book = current.book,
|
||||
onImageOpen = { imageViewer = it },
|
||||
onThemeToggle = onThemeToggle,
|
||||
onBookInfo = {
|
||||
state = AppState.BookInfo(
|
||||
fileId = current.fileId,
|
||||
book = current.book,
|
||||
libraryItems = current.libraryItems,
|
||||
scanPath = current.scanPath,
|
||||
message = current.message,
|
||||
)
|
||||
},
|
||||
onDeleted = { message ->
|
||||
state = AppState.Library(emptyList(), current.scanPath, message)
|
||||
},
|
||||
onDeleteRequested = ::requestDelete,
|
||||
onBack = ::navigateBack,
|
||||
)
|
||||
is AppState.BookInfo -> BookInfoScreen(
|
||||
fileId = current.fileId,
|
||||
book = current.book,
|
||||
onImageOpen = { imageViewer = it },
|
||||
onBack = ::navigateBack,
|
||||
)
|
||||
is AppState.Error -> ErrorScreen(current.message, onBack = ::navigateBack)
|
||||
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 = { 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,
|
||||
onStateChange = { state = it },
|
||||
onStartScan = { path ->
|
||||
startScan(path)
|
||||
state = AppState.Library(current.items, path, strings.scanning)
|
||||
},
|
||||
)
|
||||
is AppState.Reader -> BookView(
|
||||
fileId = current.fileId,
|
||||
book = current.book,
|
||||
onImageOpen = { imageViewer = it },
|
||||
onThemeToggle = onThemeToggle,
|
||||
onBookChanged = ::refreshLibraryItem,
|
||||
onBookInfo = {
|
||||
state = AppState.BookInfo(
|
||||
fileId = current.fileId,
|
||||
book = current.book,
|
||||
libraryItems = current.libraryItems,
|
||||
scanPath = current.scanPath,
|
||||
message = current.message,
|
||||
)
|
||||
},
|
||||
onDeleted = { message ->
|
||||
state = libraryBackState?.copy(message = message)
|
||||
?: AppState.Library(emptyList(), current.scanPath, message)
|
||||
libraryBackState = null
|
||||
},
|
||||
onDeleteRequested = ::requestDelete,
|
||||
onBack = ::navigateBack,
|
||||
)
|
||||
is AppState.BookInfo -> BookInfoScreen(
|
||||
fileId = current.fileId,
|
||||
book = current.book,
|
||||
onImageOpen = { imageViewer = it },
|
||||
onBack = ::navigateBack,
|
||||
)
|
||||
is AppState.Error -> ErrorScreen(current.message, onBack = ::navigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
imageViewer?.let { image ->
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user