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,
|
val restore: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internal data class LibraryItemRefreshRequest(
|
||||||
|
val id: Long,
|
||||||
|
val fileId: String,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App() {
|
fun App() {
|
||||||
@ -165,15 +170,23 @@ private fun BookReaderApp(
|
|||||||
) -> Unit,
|
) -> Unit,
|
||||||
) {
|
) {
|
||||||
var state by remember { mutableStateOf<AppState>(AppState.LoadingStartup) }
|
var state by remember { mutableStateOf<AppState>(AppState.LoadingStartup) }
|
||||||
|
var libraryBackState by remember { mutableStateOf<AppState.Library?>(null) }
|
||||||
var activeScan by remember { mutableStateOf<LibraryScanProgress?>(null) }
|
var activeScan by remember { mutableStateOf<LibraryScanProgress?>(null) }
|
||||||
var scanJob by remember { mutableStateOf<Job?>(null) }
|
var scanJob by remember { mutableStateOf<Job?>(null) }
|
||||||
var pendingDelete by remember { mutableStateOf<PendingLibraryDelete?>(null) }
|
var pendingDelete by remember { mutableStateOf<PendingLibraryDelete?>(null) }
|
||||||
var pendingDeleteJob by remember { mutableStateOf<Job?>(null) }
|
var pendingDeleteJob by remember { mutableStateOf<Job?>(null) }
|
||||||
var hiddenDeletedFileIds by remember { mutableStateOf<Set<String>>(emptySet()) }
|
var hiddenDeletedFileIds by remember { mutableStateOf<Set<String>>(emptySet()) }
|
||||||
var nextDeleteId by remember { mutableStateOf(0L) }
|
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) }
|
var imageViewer by remember { mutableStateOf<ViewedBookImage?>(null) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
fun refreshLibraryItem(fileId: String) {
|
||||||
|
nextLibraryItemRefreshId += 1
|
||||||
|
libraryItemRefreshRequest = LibraryItemRefreshRequest(nextLibraryItemRefreshId, fileId)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
state = loadStartupState()
|
state = loadStartupState()
|
||||||
}
|
}
|
||||||
@ -279,7 +292,11 @@ private fun BookReaderApp(
|
|||||||
)
|
)
|
||||||
is AppState.Reader -> {
|
is AppState.Reader -> {
|
||||||
scope.launch { saveActiveReadingFileId(null) }
|
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.Scan -> AppState.Library(current.items, current.scanPath, current.message)
|
||||||
is AppState.Error -> AppState.LoadingStartup
|
is AppState.Error -> AppState.LoadingStartup
|
||||||
@ -340,17 +357,37 @@ private fun BookReaderApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (val current = state) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
AppState.LoadingStartup -> LoadingScreen(strings.loadingOpeningBook)
|
val currentLibraryState = when (val current = state) {
|
||||||
is AppState.Library -> LibraryScreen(
|
is AppState.Library -> current
|
||||||
state = current,
|
is AppState.Reader, is AppState.BookInfo -> libraryBackState
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
currentLibraryState?.let { libraryState ->
|
||||||
|
LibraryScreen(
|
||||||
|
state = libraryState,
|
||||||
activeScan = activeScan,
|
activeScan = activeScan,
|
||||||
|
itemRefreshRequest = libraryItemRefreshRequest,
|
||||||
hiddenFileIds = hiddenDeletedFileIds + pendingDelete?.request?.fileId?.let(::setOf).orEmpty(),
|
hiddenFileIds = hiddenDeletedFileIds + pendingDelete?.request?.fileId?.let(::setOf).orEmpty(),
|
||||||
onStateChange = { state = it },
|
onStateChange = { next ->
|
||||||
onNavigateToScan = { state = AppState.Scan(current.items, current.scanPath, current.message) },
|
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,
|
onStartScan = ::startScan,
|
||||||
onDeleteRequested = ::requestDelete,
|
onDeleteRequested = ::requestDelete,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val current = state) {
|
||||||
|
AppState.LoadingStartup -> LoadingScreen(strings.loadingOpeningBook)
|
||||||
|
is AppState.Library -> Unit
|
||||||
is AppState.Scan -> ScanScreen(
|
is AppState.Scan -> ScanScreen(
|
||||||
state = current,
|
state = current,
|
||||||
activeScan = activeScan,
|
activeScan = activeScan,
|
||||||
@ -365,6 +402,7 @@ private fun BookReaderApp(
|
|||||||
book = current.book,
|
book = current.book,
|
||||||
onImageOpen = { imageViewer = it },
|
onImageOpen = { imageViewer = it },
|
||||||
onThemeToggle = onThemeToggle,
|
onThemeToggle = onThemeToggle,
|
||||||
|
onBookChanged = ::refreshLibraryItem,
|
||||||
onBookInfo = {
|
onBookInfo = {
|
||||||
state = AppState.BookInfo(
|
state = AppState.BookInfo(
|
||||||
fileId = current.fileId,
|
fileId = current.fileId,
|
||||||
@ -375,7 +413,9 @@ private fun BookReaderApp(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDeleted = { message ->
|
onDeleted = { message ->
|
||||||
state = AppState.Library(emptyList(), current.scanPath, message)
|
state = libraryBackState?.copy(message = message)
|
||||||
|
?: AppState.Library(emptyList(), current.scanPath, message)
|
||||||
|
libraryBackState = null
|
||||||
},
|
},
|
||||||
onDeleteRequested = ::requestDelete,
|
onDeleteRequested = ::requestDelete,
|
||||||
onBack = ::navigateBack,
|
onBack = ::navigateBack,
|
||||||
@ -388,6 +428,7 @@ private fun BookReaderApp(
|
|||||||
)
|
)
|
||||||
is AppState.Error -> ErrorScreen(current.message, onBack = ::navigateBack)
|
is AppState.Error -> ErrorScreen(current.message, onBack = ::navigateBack)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
imageViewer?.let { image ->
|
imageViewer?.let { image ->
|
||||||
ImageViewer(
|
ImageViewer(
|
||||||
|
|||||||
@ -82,6 +82,7 @@ import kotlinx.coroutines.launch
|
|||||||
internal fun LibraryScreen(
|
internal fun LibraryScreen(
|
||||||
state: AppState.Library,
|
state: AppState.Library,
|
||||||
activeScan: LibraryScanProgress?,
|
activeScan: LibraryScanProgress?,
|
||||||
|
itemRefreshRequest: LibraryItemRefreshRequest?,
|
||||||
hiddenFileIds: Set<String>,
|
hiddenFileIds: Set<String>,
|
||||||
onStateChange: (AppState) -> Unit,
|
onStateChange: (AppState) -> Unit,
|
||||||
onNavigateToScan: () -> Unit,
|
onNavigateToScan: () -> Unit,
|
||||||
@ -147,6 +148,13 @@ internal fun LibraryScreen(
|
|||||||
recentlyAddedItems = loadRecentlyAddedLibraryItems(since, RecentlyAddedLimit)
|
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) {
|
fun refresh(nextMessage: String? = message) {
|
||||||
message = nextMessage
|
message = nextMessage
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@ -247,6 +255,10 @@ internal fun LibraryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(itemRefreshRequest) {
|
||||||
|
itemRefreshRequest?.let { refreshLibraryItem(it.fileId) }
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(searchText) {
|
LaunchedEffect(searchText) {
|
||||||
val query = searchText
|
val query = searchText
|
||||||
if (query.isBlank()) {
|
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.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.isSpecified
|
import androidx.compose.ui.unit.isSpecified
|
||||||
import androidx.compose.ui.unit.sp
|
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.fb2.Fb2TextStyle
|
||||||
import net.sergeych.toread.text.HyphenationRegistry
|
import net.sergeych.toread.text.HyphenationRegistry
|
||||||
import net.sergeych.toread.text.SoftHyphen
|
import net.sergeych.toread.text.SoftHyphen
|
||||||
import kotlin.math.min
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@ -117,7 +117,7 @@ internal fun ContinuousBookReader(
|
|||||||
val hyphenation = remember { HyphenationRegistry() }
|
val hyphenation = remember { HyphenationRegistry() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val textLineMetricsByItem = remember(contentPlan) { mutableStateMapOf<Int, TextLineMetrics>() }
|
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) {
|
val userScrollConnection = remember(onUserScroll) {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
@ -945,6 +945,13 @@ private val isDesktopPlatform: Boolean by lazy {
|
|||||||
getPlatform().name.startsWith("Java")
|
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 {
|
private fun TextLayoutResult.endsAtSoftHyphen(text: String, line: Int): Boolean {
|
||||||
val end = getLineEnd(line, visibleEnd = false)
|
val end = getLineEnd(line, visibleEnd = false)
|
||||||
return text.getOrNull(end - 1) == SoftHyphen || text.getOrNull(end) == SoftHyphen
|
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 imageAspectRatio = bitmap.width.toFloat() / bitmap.height.coerceAtLeast(1).toFloat()
|
||||||
val imageModifier = if (fitBackgroundToBitmapBounds) {
|
val imageModifier = if (fitBackgroundToBitmapBounds) {
|
||||||
val bitmapWidth = with(density) { bitmap.width.toDp() }
|
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
|
Modifier
|
||||||
.width(if (bitmapWidth < maxWidth) bitmapWidth else maxWidth)
|
.width(displayWidth)
|
||||||
.aspectRatio(imageAspectRatio)
|
.aspectRatio(imageAspectRatio)
|
||||||
} else {
|
} else {
|
||||||
Modifier.fillMaxSize()
|
Modifier.fillMaxSize()
|
||||||
|
|||||||
@ -66,6 +66,7 @@ internal fun BookView(
|
|||||||
book: Fb2Book,
|
book: Fb2Book,
|
||||||
onImageOpen: (ViewedBookImage) -> Unit,
|
onImageOpen: (ViewedBookImage) -> Unit,
|
||||||
onThemeToggle: () -> Unit,
|
onThemeToggle: () -> Unit,
|
||||||
|
onBookChanged: (String) -> Unit,
|
||||||
onBookInfo: () -> Unit,
|
onBookInfo: () -> Unit,
|
||||||
onDeleted: (String?) -> Unit,
|
onDeleted: (String?) -> Unit,
|
||||||
onDeleteRequested: (
|
onDeleteRequested: (
|
||||||
@ -110,6 +111,7 @@ internal fun BookView(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
if (markLibraryReadingStatus(fileId, status)) {
|
if (markLibraryReadingStatus(fileId, status)) {
|
||||||
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = status)
|
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = status)
|
||||||
|
onBookChanged(fileId)
|
||||||
if (status == BookReadingStatus.READ) markedRead = true
|
if (status == BookReadingStatus.READ) markedRead = true
|
||||||
if (status == BookReadingStatus.NEW) markedRead = false
|
if (status == BookReadingStatus.NEW) markedRead = false
|
||||||
if (status == BookReadingStatus.NOT_INTERESTED) {
|
if (status == BookReadingStatus.NOT_INTERESTED) {
|
||||||
@ -134,6 +136,7 @@ internal fun BookView(
|
|||||||
markedRead = item?.readingStatus == BookReadingStatus.READ
|
markedRead = item?.readingStatus == BookReadingStatus.READ
|
||||||
if (item?.readingStatus?.shouldBecomeReadingOnOpen() == true && markLibraryReadingStatus(fileId, BookReadingStatus.READING)) {
|
if (item?.readingStatus?.shouldBecomeReadingOnOpen() == true && markLibraryReadingStatus(fileId, BookReadingStatus.READING)) {
|
||||||
libraryItem = loadLibraryItem(fileId) ?: item.copy(readingStatus = BookReadingStatus.READING)
|
libraryItem = loadLibraryItem(fileId) ?: item.copy(readingStatus = BookReadingStatus.READING)
|
||||||
|
onBookChanged(fileId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +185,7 @@ internal fun BookView(
|
|||||||
markedRead = true
|
markedRead = true
|
||||||
if (markLibraryReadingStatus(fileId, BookReadingStatus.READ)) {
|
if (markLibraryReadingStatus(fileId, BookReadingStatus.READ)) {
|
||||||
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = BookReadingStatus.READ)
|
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = BookReadingStatus.READ)
|
||||||
|
onBookChanged(fileId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,6 +234,7 @@ internal fun BookView(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
if (markLibraryFavorite(fileId, favorite)) {
|
if (markLibraryFavorite(fileId, favorite)) {
|
||||||
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(favorite = favorite)
|
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(favorite = favorite)
|
||||||
|
onBookChanged(fileId)
|
||||||
showMessage(if (favorite) strings.addedToFavorites() else strings.removedFromFavorites())
|
showMessage(if (favorite) strings.addedToFavorites() else strings.removedFromFavorites())
|
||||||
} else {
|
} else {
|
||||||
showMessage(strings.couldNotUpdateBook)
|
showMessage(strings.couldNotUpdateBook)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user