Add library categories and favorites
This commit is contained in:
parent
d749352333
commit
02bde7e644
@ -337,6 +337,12 @@ actual suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual suspend fun markLibraryFavorite(fileId: String, favorite: Boolean): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
openLibraryDatabase().useLibrary { db ->
|
||||||
|
db.files.updateFavorite(fileId, favorite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual suspend fun shareLibraryBookFile(fileId: String): Boolean = withContext(Dispatchers.IO) {
|
actual suspend fun shareLibraryBookFile(fileId: String): Boolean = withContext(Dispatchers.IO) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val shareFile = openLibraryDatabase().useLibrary { db ->
|
val shareFile = openLibraryDatabase().useLibrary { db ->
|
||||||
@ -698,6 +704,7 @@ private fun LibraryFileRecord.toLibraryItem(): LibraryItem =
|
|||||||
storageUri = storageUri,
|
storageUri = storageUri,
|
||||||
lastSeenAt = lastSeenAt,
|
lastSeenAt = lastSeenAt,
|
||||||
readingStatus = readingStatus,
|
readingStatus = readingStatus,
|
||||||
|
favorite = favorite,
|
||||||
lastReadAt = lastReadAt,
|
lastReadAt = lastReadAt,
|
||||||
importedAt = importedAt,
|
importedAt = importedAt,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ data class LibraryItem(
|
|||||||
val storageUri: String?,
|
val storageUri: String?,
|
||||||
val lastSeenAt: Long?,
|
val lastSeenAt: Long?,
|
||||||
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
||||||
|
val favorite: Boolean = false,
|
||||||
val lastReadAt: Long? = null,
|
val lastReadAt: Long? = null,
|
||||||
val importedAt: Long? = null,
|
val importedAt: Long? = null,
|
||||||
val coverImage: ByteArray? = null,
|
val coverImage: ByteArray? = null,
|
||||||
@ -124,6 +125,8 @@ expect suspend fun clearLibraryReadingPosition(fileId: String)
|
|||||||
|
|
||||||
expect suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingStatus): Boolean
|
expect suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingStatus): Boolean
|
||||||
|
|
||||||
|
expect suspend fun markLibraryFavorite(fileId: String, favorite: Boolean): Boolean
|
||||||
|
|
||||||
expect suspend fun shareLibraryBookFile(fileId: String): Boolean
|
expect suspend fun shareLibraryBookFile(fileId: String): Boolean
|
||||||
|
|
||||||
expect suspend fun viewLibraryBookFile(fileId: String): Boolean
|
expect suspend fun viewLibraryBookFile(fileId: String): Boolean
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
@ -53,6 +54,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
@ -60,6 +62,7 @@ import androidx.compose.ui.input.key.key
|
|||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -97,10 +100,14 @@ internal fun LibraryScreen(
|
|||||||
var settingsMenuOpen by remember { mutableStateOf(false) }
|
var settingsMenuOpen by remember { mutableStateOf(false) }
|
||||||
var autoScanDownloads by remember { mutableStateOf(true) }
|
var autoScanDownloads by remember { mutableStateOf(true) }
|
||||||
var autoScanSettingLoaded by remember { mutableStateOf(false) }
|
var autoScanSettingLoaded by remember { mutableStateOf(false) }
|
||||||
|
var backgroundDownloadsRescanStarted by remember { mutableStateOf(false) }
|
||||||
var searchText by remember { mutableStateOf("") }
|
var searchText by remember { mutableStateOf("") }
|
||||||
var searchResults by remember { mutableStateOf<List<LibraryItem>>(emptyList()) }
|
var searchResults by remember { mutableStateOf<List<LibraryItem>>(emptyList()) }
|
||||||
var searching by remember { mutableStateOf(false) }
|
var searching by remember { mutableStateOf(false) }
|
||||||
var readingNowCollapsed by remember { mutableStateOf(false) }
|
var readingNowCollapsed by remember { mutableStateOf(false) }
|
||||||
|
var favoritesCollapsed by remember { mutableStateOf(false) }
|
||||||
|
var toReadCollapsed by remember { mutableStateOf(false) }
|
||||||
|
var readCollapsed by remember { mutableStateOf(false) }
|
||||||
var recentlyAddedCollapsed by remember { mutableStateOf(false) }
|
var recentlyAddedCollapsed by remember { mutableStateOf(false) }
|
||||||
var myLibraryCollapsed by remember { mutableStateOf(false) }
|
var myLibraryCollapsed by remember { mutableStateOf(false) }
|
||||||
var notInterestedCollapsed by remember { mutableStateOf(false) }
|
var notInterestedCollapsed by remember { mutableStateOf(false) }
|
||||||
@ -171,6 +178,31 @@ internal fun LibraryScreen(
|
|||||||
val updatedItem = loadLibraryItem(item.fileId) ?: item.copy(readingStatus = status)
|
val updatedItem = loadLibraryItem(item.fileId) ?: item.copy(readingStatus = status)
|
||||||
items = items.replaceLibraryItem(updatedItem)
|
items = items.replaceLibraryItem(updatedItem)
|
||||||
searchResults = searchResults.replaceLibraryItem(updatedItem)
|
searchResults = searchResults.replaceLibraryItem(updatedItem)
|
||||||
|
recentlyAddedItems = recentlyAddedItems.replaceLibraryItem(updatedItem)
|
||||||
|
message = successMessage
|
||||||
|
if (searchActive) {
|
||||||
|
searching = true
|
||||||
|
searchResults = searchLibraryItems(searchText, SearchResultLimit)
|
||||||
|
searching = false
|
||||||
|
} else {
|
||||||
|
loadPage(reset = true)
|
||||||
|
loadRecentlyAdded()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = "Could not update ${item.title}."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun applyFavorite(
|
||||||
|
item: LibraryItem,
|
||||||
|
favorite: Boolean,
|
||||||
|
successMessage: String,
|
||||||
|
) {
|
||||||
|
if (markLibraryFavorite(item.fileId, favorite)) {
|
||||||
|
val updatedItem = loadLibraryItem(item.fileId) ?: item.copy(favorite = favorite)
|
||||||
|
items = items.replaceLibraryItem(updatedItem)
|
||||||
|
searchResults = searchResults.replaceLibraryItem(updatedItem)
|
||||||
|
recentlyAddedItems = recentlyAddedItems.replaceLibraryItem(updatedItem)
|
||||||
message = successMessage
|
message = successMessage
|
||||||
if (searchActive) {
|
if (searchActive) {
|
||||||
searching = true
|
searching = true
|
||||||
@ -246,10 +278,18 @@ internal fun LibraryScreen(
|
|||||||
autoScanSettingLoaded = true
|
autoScanSettingLoaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(autoScanSettingLoaded, autoScanDownloads) {
|
LaunchedEffect(autoScanSettingLoaded, autoScanDownloads, activeScan == null) {
|
||||||
if (!autoScanSettingLoaded || !autoScanDownloads || activeScan != null) return@LaunchedEffect
|
if (
|
||||||
|
!autoScanSettingLoaded ||
|
||||||
|
!autoScanDownloads ||
|
||||||
|
backgroundDownloadsRescanStarted ||
|
||||||
|
activeScan != null
|
||||||
|
) {
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
if (loadDownloadsWasScanned()) {
|
if (loadDownloadsWasScanned()) {
|
||||||
downloadsScanPath()?.let { path ->
|
downloadsScanPath()?.let { path ->
|
||||||
|
backgroundDownloadsRescanStarted = true
|
||||||
message = "Scanning Downloads..."
|
message = "Scanning Downloads..."
|
||||||
onStartScan(path)
|
onStartScan(path)
|
||||||
}
|
}
|
||||||
@ -260,7 +300,7 @@ internal fun LibraryScreen(
|
|||||||
if (activeScan != null) {
|
if (activeScan != null) {
|
||||||
wasScanning = true
|
wasScanning = true
|
||||||
while (true) {
|
while (true) {
|
||||||
delay(5_000)
|
delay(2_000)
|
||||||
loadPage(reset = true)
|
loadPage(reset = true)
|
||||||
loadRecentlyAdded()
|
loadRecentlyAdded()
|
||||||
}
|
}
|
||||||
@ -364,7 +404,9 @@ internal fun LibraryScreen(
|
|||||||
readerLibraryItems = readerLibraryItems.replaceLibraryItem(updatedItem)
|
readerLibraryItems = readerLibraryItems.replaceLibraryItem(updatedItem)
|
||||||
coverCache[updatedItem.fileId] = loadLibraryItemCover(updatedItem.fileId)
|
coverCache[updatedItem.fileId] = loadLibraryItemCover(updatedItem.fileId)
|
||||||
}
|
}
|
||||||
if (markLibraryReadingStatus(item.fileId, BookReadingStatus.READING)) {
|
if (item.readingStatus == BookReadingStatus.NEW &&
|
||||||
|
markLibraryReadingStatus(item.fileId, BookReadingStatus.READING)
|
||||||
|
) {
|
||||||
val readingItem = loadLibraryItem(item.fileId)
|
val readingItem = loadLibraryItem(item.fileId)
|
||||||
?: item.copy(readingStatus = BookReadingStatus.READING)
|
?: item.copy(readingStatus = BookReadingStatus.READING)
|
||||||
items = items.replaceLibraryItem(readingItem)
|
items = items.replaceLibraryItem(readingItem)
|
||||||
@ -418,6 +460,16 @@ internal fun LibraryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onMarkToRead = {
|
||||||
|
scope.launch {
|
||||||
|
busy = true
|
||||||
|
try {
|
||||||
|
applyReadingStatus(item, BookReadingStatus.TO_READ, "Marked ${item.title} to read.")
|
||||||
|
} finally {
|
||||||
|
busy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onNotInterested = {
|
onNotInterested = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
busy = true
|
busy = true
|
||||||
@ -428,6 +480,17 @@ internal fun LibraryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onFavoriteChange = { favorite ->
|
||||||
|
scope.launch {
|
||||||
|
busy = true
|
||||||
|
try {
|
||||||
|
val label = if (favorite) "Added ${item.title} to favorites." else "Removed ${item.title} from favorites."
|
||||||
|
applyFavorite(item, favorite, label)
|
||||||
|
} finally {
|
||||||
|
busy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onDelete = {
|
onDelete = {
|
||||||
val previousItems = items
|
val previousItems = items
|
||||||
val previousSearchResults = searchResults
|
val previousSearchResults = searchResults
|
||||||
@ -473,6 +536,9 @@ internal fun LibraryScreen(
|
|||||||
libraryRows("search", visibleItems)
|
libraryRows("search", visibleItems)
|
||||||
} else {
|
} else {
|
||||||
val readingNow = visibleItems.filter { it.readingStatus == BookReadingStatus.READING }
|
val readingNow = visibleItems.filter { it.readingStatus == BookReadingStatus.READING }
|
||||||
|
val favorites = visibleItems.filter { it.favorite }
|
||||||
|
val toRead = visibleItems.filter { it.readingStatus == BookReadingStatus.TO_READ }
|
||||||
|
val read = visibleItems.filter { it.readingStatus == BookReadingStatus.READ }
|
||||||
val recentlyAdded = recentlyAddedItems.filter {
|
val recentlyAdded = recentlyAddedItems.filter {
|
||||||
it.fileId !in hiddenFileIds && it.readingStatus == BookReadingStatus.NEW
|
it.fileId !in hiddenFileIds && it.readingStatus == BookReadingStatus.NEW
|
||||||
}
|
}
|
||||||
@ -480,6 +546,8 @@ internal fun LibraryScreen(
|
|||||||
val myLibrary = visibleItems.filter {
|
val myLibrary = visibleItems.filter {
|
||||||
it.fileId !in recentlyAddedIds &&
|
it.fileId !in recentlyAddedIds &&
|
||||||
it.readingStatus != BookReadingStatus.READING &&
|
it.readingStatus != BookReadingStatus.READING &&
|
||||||
|
it.readingStatus != BookReadingStatus.TO_READ &&
|
||||||
|
it.readingStatus != BookReadingStatus.READ &&
|
||||||
it.readingStatus != BookReadingStatus.NOT_INTERESTED
|
it.readingStatus != BookReadingStatus.NOT_INTERESTED
|
||||||
}
|
}
|
||||||
val notInterested = visibleItems.filter { it.readingStatus == BookReadingStatus.NOT_INTERESTED }
|
val notInterested = visibleItems.filter { it.readingStatus == BookReadingStatus.NOT_INTERESTED }
|
||||||
@ -494,6 +562,26 @@ internal fun LibraryScreen(
|
|||||||
) {
|
) {
|
||||||
libraryRows("reading", readingNow)
|
libraryRows("reading", readingNow)
|
||||||
}
|
}
|
||||||
|
librarySection(
|
||||||
|
key = "favorites",
|
||||||
|
title = "favorites",
|
||||||
|
itemCount = favorites.size,
|
||||||
|
displayCount = favorites.size.takeIf { endReached },
|
||||||
|
collapsed = favoritesCollapsed,
|
||||||
|
onCollapsedChange = { favoritesCollapsed = it },
|
||||||
|
) {
|
||||||
|
libraryRows("favorites", favorites)
|
||||||
|
}
|
||||||
|
librarySection(
|
||||||
|
key = "to-read",
|
||||||
|
title = "to read",
|
||||||
|
itemCount = toRead.size,
|
||||||
|
displayCount = toRead.size.takeIf { endReached },
|
||||||
|
collapsed = toReadCollapsed,
|
||||||
|
onCollapsedChange = { toReadCollapsed = it },
|
||||||
|
) {
|
||||||
|
libraryRows("to-read", toRead)
|
||||||
|
}
|
||||||
librarySection(
|
librarySection(
|
||||||
key = "recently-added",
|
key = "recently-added",
|
||||||
title = "recently added",
|
title = "recently added",
|
||||||
@ -514,6 +602,16 @@ internal fun LibraryScreen(
|
|||||||
) {
|
) {
|
||||||
libraryRows("library", myLibrary)
|
libraryRows("library", myLibrary)
|
||||||
}
|
}
|
||||||
|
librarySection(
|
||||||
|
key = "read",
|
||||||
|
title = "read",
|
||||||
|
itemCount = read.size,
|
||||||
|
displayCount = read.size.takeIf { endReached },
|
||||||
|
collapsed = readCollapsed,
|
||||||
|
onCollapsedChange = { readCollapsed = it },
|
||||||
|
) {
|
||||||
|
libraryRows("read", read)
|
||||||
|
}
|
||||||
librarySection(
|
librarySection(
|
||||||
key = "not-interested",
|
key = "not-interested",
|
||||||
title = "not interested",
|
title = "not interested",
|
||||||
@ -723,6 +821,8 @@ private fun LibraryRow(
|
|||||||
actions: LibraryItemActions,
|
actions: LibraryItemActions,
|
||||||
) {
|
) {
|
||||||
var menuOpen by remember { mutableStateOf(false) }
|
var menuOpen by remember { mutableStateOf(false) }
|
||||||
|
val titleStyle = MaterialTheme.typography.titleMedium
|
||||||
|
val favoriteIconSize = with(LocalDensity.current) { titleStyle.lineHeight.toDp() - 2.dp }
|
||||||
|
|
||||||
Card(shape = RoundedCornerShape(6.dp), colors = quietCardColors(), modifier = Modifier.fillMaxWidth()) {
|
Card(shape = RoundedCornerShape(6.dp), colors = quietCardColors(), modifier = Modifier.fillMaxWidth()) {
|
||||||
Row(
|
Row(
|
||||||
@ -735,13 +835,27 @@ private fun LibraryRow(
|
|||||||
) {
|
) {
|
||||||
LibraryCover(item, coverCache, modifier = Modifier.width(46.dp).aspectRatio(0.68f))
|
LibraryCover(item, coverCache, modifier = Modifier.width(46.dp).aspectRatio(0.68f))
|
||||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
item.title,
|
item.title,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = titleStyle,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
)
|
)
|
||||||
|
if (item.favorite) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Favorite,
|
||||||
|
contentDescription = "Favorite",
|
||||||
|
tint = Color(0xFFD32F2F),
|
||||||
|
modifier = Modifier.size(favoriteIconSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
item.authors.joinToString().ifBlank { "Unknown author" },
|
item.authors.joinToString().ifBlank { "Unknown author" },
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
@ -787,15 +901,31 @@ private fun LibraryRow(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (item.readingStatus != BookReadingStatus.TO_READ) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Mark to read") },
|
||||||
|
onClick = {
|
||||||
|
menuOpen = false
|
||||||
|
actions.onMarkToRead()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
if (item.readingStatus != BookReadingStatus.NEW) {
|
if (item.readingStatus != BookReadingStatus.NEW) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Remove marks") },
|
text = { Text(if (item.readingStatus == BookReadingStatus.TO_READ) "Remove to read" else "Remove marks") },
|
||||||
onClick = {
|
onClick = {
|
||||||
menuOpen = false
|
menuOpen = false
|
||||||
actions.onRemoveMarks()
|
actions.onRemoveMarks()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(if (item.favorite) "Remove favorite" else "Add favorite") },
|
||||||
|
onClick = {
|
||||||
|
menuOpen = false
|
||||||
|
actions.onFavoriteChange(!item.favorite)
|
||||||
|
},
|
||||||
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Not interested") },
|
text = { Text("Not interested") },
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -822,7 +952,9 @@ private data class LibraryItemActions(
|
|||||||
val onMarkAsRead: () -> Unit,
|
val onMarkAsRead: () -> Unit,
|
||||||
val onMarkAsUnread: () -> Unit,
|
val onMarkAsUnread: () -> Unit,
|
||||||
val onRemoveMarks: () -> Unit,
|
val onRemoveMarks: () -> Unit,
|
||||||
|
val onMarkToRead: () -> Unit,
|
||||||
val onNotInterested: () -> Unit,
|
val onNotInterested: () -> Unit,
|
||||||
|
val onFavoriteChange: (Boolean) -> Unit,
|
||||||
val onDelete: () -> Unit,
|
val onDelete: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -866,6 +998,7 @@ private fun LibraryCover(
|
|||||||
|
|
||||||
private fun LibraryItem.libraryMetadataLine(): String =
|
private fun LibraryItem.libraryMetadataLine(): String =
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
|
"Favorite".takeIf { favorite },
|
||||||
readingStatus.displayLabel,
|
readingStatus.displayLabel,
|
||||||
lastReadAt?.formatLastRead(),
|
lastReadAt?.formatLastRead(),
|
||||||
date?.yearOrRaw(),
|
date?.yearOrRaw(),
|
||||||
@ -877,6 +1010,7 @@ private fun LibraryItem.libraryMetadataLine(): String =
|
|||||||
private val BookReadingStatus.displayLabel: String
|
private val BookReadingStatus.displayLabel: String
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
BookReadingStatus.NEW -> "New"
|
BookReadingStatus.NEW -> "New"
|
||||||
|
BookReadingStatus.TO_READ -> "To read"
|
||||||
BookReadingStatus.READING -> "Reading"
|
BookReadingStatus.READING -> "Reading"
|
||||||
BookReadingStatus.READ -> "Read"
|
BookReadingStatus.READ -> "Read"
|
||||||
BookReadingStatus.NOT_INTERESTED -> "Not interested"
|
BookReadingStatus.NOT_INTERESTED -> "Not interested"
|
||||||
|
|||||||
@ -81,6 +81,7 @@ internal fun BookView(
|
|||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
var restored by remember(fileId) { mutableStateOf(false) }
|
var restored by remember(fileId) { mutableStateOf(false) }
|
||||||
var markedRead by remember(fileId) { mutableStateOf(false) }
|
var markedRead by remember(fileId) { mutableStateOf(false) }
|
||||||
|
var libraryItem by remember(fileId) { mutableStateOf<LibraryItem?>(null) }
|
||||||
var readAloudPanelVisible by remember(fileId) { mutableStateOf(false) }
|
var readAloudPanelVisible by remember(fileId) { mutableStateOf(false) }
|
||||||
var readAloudSettingsVisible by remember(fileId) { mutableStateOf(false) }
|
var readAloudSettingsVisible by remember(fileId) { mutableStateOf(false) }
|
||||||
val readAloudState by ReadAloudPlatform.state.collectAsState()
|
val readAloudState by ReadAloudPlatform.state.collectAsState()
|
||||||
@ -102,6 +103,7 @@ internal fun BookView(
|
|||||||
fun setReadingStatus(status: BookReadingStatus, successMessage: String) {
|
fun setReadingStatus(status: BookReadingStatus, successMessage: String) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (markLibraryReadingStatus(fileId, status)) {
|
if (markLibraryReadingStatus(fileId, status)) {
|
||||||
|
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = status)
|
||||||
if (status == BookReadingStatus.READ) markedRead = true
|
if (status == BookReadingStatus.READ) markedRead = true
|
||||||
if (status == BookReadingStatus.NEW) markedRead = false
|
if (status == BookReadingStatus.NEW) markedRead = false
|
||||||
showMessage(successMessage)
|
showMessage(successMessage)
|
||||||
@ -112,7 +114,12 @@ internal fun BookView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(fileId) {
|
LaunchedEffect(fileId) {
|
||||||
markLibraryReadingStatus(fileId, BookReadingStatus.READING)
|
val item = loadLibraryItem(fileId)
|
||||||
|
libraryItem = item
|
||||||
|
markedRead = item?.readingStatus == BookReadingStatus.READ
|
||||||
|
if (item?.readingStatus == BookReadingStatus.NEW && markLibraryReadingStatus(fileId, BookReadingStatus.READING)) {
|
||||||
|
libraryItem = loadLibraryItem(fileId) ?: item.copy(readingStatus = BookReadingStatus.READING)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(fileId) {
|
DisposableEffect(fileId) {
|
||||||
@ -144,10 +151,12 @@ internal fun BookView(
|
|||||||
val lastVisible = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
|
val lastVisible = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
|
||||||
layoutInfo.totalItemsCount > 0 && lastVisible >= layoutInfo.totalItemsCount - 1
|
layoutInfo.totalItemsCount > 0 && lastVisible >= layoutInfo.totalItemsCount - 1
|
||||||
}
|
}
|
||||||
.filter { restored && it && !markedRead }
|
.filter { restored && it && !markedRead && libraryItem?.readingStatus != BookReadingStatus.TO_READ }
|
||||||
.collect {
|
.collect {
|
||||||
markedRead = true
|
markedRead = true
|
||||||
markLibraryReadingStatus(fileId, BookReadingStatus.READ)
|
if (markLibraryReadingStatus(fileId, BookReadingStatus.READ)) {
|
||||||
|
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(readingStatus = BookReadingStatus.READ)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,12 +188,27 @@ internal fun BookView(
|
|||||||
onMarkAsRead = {
|
onMarkAsRead = {
|
||||||
setReadingStatus(BookReadingStatus.READ, "Marked as read.")
|
setReadingStatus(BookReadingStatus.READ, "Marked as read.")
|
||||||
},
|
},
|
||||||
|
onMarkToRead = {
|
||||||
|
setReadingStatus(BookReadingStatus.TO_READ, "Marked to read.")
|
||||||
|
},
|
||||||
onNotInterested = {
|
onNotInterested = {
|
||||||
setReadingStatus(BookReadingStatus.NOT_INTERESTED, "Marked as not interested.")
|
setReadingStatus(BookReadingStatus.NOT_INTERESTED, "Marked as not interested.")
|
||||||
},
|
},
|
||||||
onClearMarks = {
|
onClearMarks = {
|
||||||
setReadingStatus(BookReadingStatus.NEW, "Cleared marks.")
|
setReadingStatus(BookReadingStatus.NEW, "Cleared marks.")
|
||||||
},
|
},
|
||||||
|
readingStatus = libraryItem?.readingStatus,
|
||||||
|
favorite = libraryItem?.favorite == true,
|
||||||
|
onFavoriteChange = { favorite ->
|
||||||
|
scope.launch {
|
||||||
|
if (markLibraryFavorite(fileId, favorite)) {
|
||||||
|
libraryItem = loadLibraryItem(fileId) ?: libraryItem?.copy(favorite = favorite)
|
||||||
|
showMessage(if (favorite) "Added to favorites." else "Removed from favorites.")
|
||||||
|
} else {
|
||||||
|
showMessage("Could not update book.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
showShareAction = showShareAction,
|
showShareAction = showShareAction,
|
||||||
onShare = {
|
onShare = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@ -290,8 +314,12 @@ private fun CompactReaderTopBar(
|
|||||||
onThemeToggle: () -> Unit,
|
onThemeToggle: () -> Unit,
|
||||||
onBookInfo: () -> Unit,
|
onBookInfo: () -> Unit,
|
||||||
onMarkAsRead: () -> Unit,
|
onMarkAsRead: () -> Unit,
|
||||||
|
onMarkToRead: () -> Unit,
|
||||||
onNotInterested: () -> Unit,
|
onNotInterested: () -> Unit,
|
||||||
onClearMarks: () -> Unit,
|
onClearMarks: () -> Unit,
|
||||||
|
readingStatus: BookReadingStatus?,
|
||||||
|
favorite: Boolean,
|
||||||
|
onFavoriteChange: (Boolean) -> Unit,
|
||||||
showShareAction: Boolean,
|
showShareAction: Boolean,
|
||||||
onShare: () -> Unit,
|
onShare: () -> Unit,
|
||||||
showViewFileAction: Boolean,
|
showViewFileAction: Boolean,
|
||||||
@ -345,6 +373,23 @@ private fun CompactReaderTopBar(
|
|||||||
onMarkAsRead()
|
onMarkAsRead()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if (readingStatus == BookReadingStatus.TO_READ) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Remove to read") },
|
||||||
|
onClick = {
|
||||||
|
menuOpen = false
|
||||||
|
onClearMarks()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Mark to read") },
|
||||||
|
onClick = {
|
||||||
|
menuOpen = false
|
||||||
|
onMarkToRead()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Not interested") },
|
text = { Text("Not interested") },
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -359,6 +404,13 @@ private fun CompactReaderTopBar(
|
|||||||
onClearMarks()
|
onClearMarks()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(if (favorite) "Remove favorite" else "Add favorite") },
|
||||||
|
onClick = {
|
||||||
|
menuOpen = false
|
||||||
|
onFavoriteChange(!favorite)
|
||||||
|
},
|
||||||
|
)
|
||||||
if (showShareAction || showViewFileAction) {
|
if (showShareAction || showViewFileAction) {
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -285,6 +285,12 @@ actual suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual suspend fun markLibraryFavorite(fileId: String, favorite: Boolean): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
openLibraryDatabase().useLibrary { db ->
|
||||||
|
db.files.updateFavorite(fileId, favorite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual suspend fun shareLibraryBookFile(fileId: String): Boolean = false
|
actual suspend fun shareLibraryBookFile(fileId: String): Boolean = false
|
||||||
|
|
||||||
actual suspend fun viewLibraryBookFile(fileId: String): Boolean = withContext(Dispatchers.IO) {
|
actual suspend fun viewLibraryBookFile(fileId: String): Boolean = withContext(Dispatchers.IO) {
|
||||||
@ -495,6 +501,7 @@ private fun LibraryFileRecord.toLibraryItem(): LibraryItem =
|
|||||||
storageUri = storageUri,
|
storageUri = storageUri,
|
||||||
lastSeenAt = lastSeenAt,
|
lastSeenAt = lastSeenAt,
|
||||||
readingStatus = readingStatus,
|
readingStatus = readingStatus,
|
||||||
|
favorite = favorite,
|
||||||
lastReadAt = lastReadAt,
|
lastReadAt = lastReadAt,
|
||||||
importedAt = importedAt,
|
importedAt = importedAt,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -57,6 +57,8 @@ actual suspend fun clearLibraryReadingPosition(fileId: String) = Unit
|
|||||||
|
|
||||||
actual suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingStatus): Boolean = false
|
actual suspend fun markLibraryReadingStatus(fileId: String, status: BookReadingStatus): Boolean = false
|
||||||
|
|
||||||
|
actual suspend fun markLibraryFavorite(fileId: String, favorite: Boolean): Boolean = false
|
||||||
|
|
||||||
actual suspend fun shareLibraryBookFile(fileId: String): Boolean = false
|
actual suspend fun shareLibraryBookFile(fileId: String): Boolean = false
|
||||||
|
|
||||||
actual suspend fun viewLibraryBookFile(fileId: String): Boolean = false
|
actual suspend fun viewLibraryBookFile(fileId: String): Boolean = false
|
||||||
|
|||||||
@ -17,6 +17,7 @@ enum class BookImportPolicy {
|
|||||||
|
|
||||||
enum class BookReadingStatus {
|
enum class BookReadingStatus {
|
||||||
NEW,
|
NEW,
|
||||||
|
TO_READ,
|
||||||
READING,
|
READING,
|
||||||
READ,
|
READ,
|
||||||
NOT_INTERESTED,
|
NOT_INTERESTED,
|
||||||
@ -109,6 +110,7 @@ data class BookFileRecord(
|
|||||||
val lastModifiedMillis: Long? = null,
|
val lastModifiedMillis: Long? = null,
|
||||||
val lastSeenAt: Long? = null,
|
val lastSeenAt: Long? = null,
|
||||||
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
||||||
|
val favorite: Boolean = false,
|
||||||
val lastReadAt: Long? = null,
|
val lastReadAt: Long? = null,
|
||||||
val createdAt: Long,
|
val createdAt: Long,
|
||||||
val updatedAt: Long,
|
val updatedAt: Long,
|
||||||
@ -128,6 +130,7 @@ data class LibraryFileRecord(
|
|||||||
val storageUri: String? = null,
|
val storageUri: String? = null,
|
||||||
val lastSeenAt: Long? = null,
|
val lastSeenAt: Long? = null,
|
||||||
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
||||||
|
val favorite: Boolean = false,
|
||||||
val lastReadAt: Long? = null,
|
val lastReadAt: Long? = null,
|
||||||
val importedAt: Long,
|
val importedAt: Long,
|
||||||
)
|
)
|
||||||
@ -210,6 +213,7 @@ interface BookFileRepository {
|
|||||||
fun list(limit: Int = 500, offset: Int = 0): List<BookFileRecord>
|
fun list(limit: Int = 500, offset: Int = 0): List<BookFileRecord>
|
||||||
fun listForBook(bookId: String): List<BookFileRecord>
|
fun listForBook(bookId: String): List<BookFileRecord>
|
||||||
fun updateReadingStatus(id: String, status: BookReadingStatus): Boolean
|
fun updateReadingStatus(id: String, status: BookReadingStatus): Boolean
|
||||||
|
fun updateFavorite(id: String, favorite: Boolean): Boolean
|
||||||
fun touchLastReadAt(id: String, lastReadAt: Long): Boolean
|
fun touchLastReadAt(id: String, lastReadAt: Long): Boolean
|
||||||
fun delete(id: String): Boolean
|
fun delete(id: String): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -213,6 +213,7 @@ private fun migrate(connection: Connection) {
|
|||||||
last_modified_millis BIGINT,
|
last_modified_millis BIGINT,
|
||||||
last_seen_at BIGINT,
|
last_seen_at BIGINT,
|
||||||
reading_status VARCHAR NOT NULL DEFAULT 'NEW',
|
reading_status VARCHAR NOT NULL DEFAULT 'NEW',
|
||||||
|
favorite BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
last_read_at BIGINT,
|
last_read_at BIGINT,
|
||||||
imported_at BIGINT NOT NULL,
|
imported_at BIGINT NOT NULL,
|
||||||
created_at BIGINT NOT NULL,
|
created_at BIGINT NOT NULL,
|
||||||
@ -225,6 +226,7 @@ private fun migrate(connection: Connection) {
|
|||||||
)
|
)
|
||||||
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS duplicate_of_file_id VARCHAR")
|
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS duplicate_of_file_id VARCHAR")
|
||||||
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS reading_status VARCHAR NOT NULL DEFAULT 'NEW'")
|
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS reading_status VARCHAR NOT NULL DEFAULT 'NEW'")
|
||||||
|
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS favorite BOOLEAN NOT NULL DEFAULT FALSE")
|
||||||
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS last_read_at BIGINT")
|
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS last_read_at BIGINT")
|
||||||
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS imported_at BIGINT")
|
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS imported_at BIGINT")
|
||||||
connection.prepareStatement("UPDATE book_files SET imported_at = ? WHERE imported_at IS NULL").use { update ->
|
connection.prepareStatement("UPDATE book_files SET imported_at = ? WHERE imported_at IS NULL").use { update ->
|
||||||
@ -487,9 +489,9 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
MERGE INTO book_files(
|
MERGE INTO book_files(
|
||||||
id, book_id, body_id, body_cluster_id, duplicate_of_file_id, raw_sha256, format,
|
id, book_id, body_id, body_cluster_id, duplicate_of_file_id, raw_sha256, format,
|
||||||
mime_type, size_bytes, original_filename, storage_kind, storage_uri, content_object_id,
|
mime_type, size_bytes, original_filename, storage_kind, storage_uri, content_object_id,
|
||||||
last_modified_millis, last_seen_at, reading_status, last_read_at, imported_at, created_at, updated_at
|
last_modified_millis, last_seen_at, reading_status, favorite, last_read_at, imported_at, created_at, updated_at
|
||||||
)
|
)
|
||||||
KEY(id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
KEY(id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
).use { statement ->
|
).use { statement ->
|
||||||
statement.setString(1, file.id)
|
statement.setString(1, file.id)
|
||||||
@ -508,10 +510,11 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
statement.setLongOrNull(14, file.lastModifiedMillis)
|
statement.setLongOrNull(14, file.lastModifiedMillis)
|
||||||
statement.setLongOrNull(15, file.lastSeenAt)
|
statement.setLongOrNull(15, file.lastSeenAt)
|
||||||
statement.setString(16, file.readingStatus.name)
|
statement.setString(16, file.readingStatus.name)
|
||||||
statement.setLongOrNull(17, file.lastReadAt)
|
statement.setBoolean(17, file.favorite)
|
||||||
statement.setLong(18, file.importedAt)
|
statement.setLongOrNull(18, file.lastReadAt)
|
||||||
statement.setLong(19, file.createdAt)
|
statement.setLong(19, file.importedAt)
|
||||||
statement.setLong(20, file.updatedAt)
|
statement.setLong(20, file.createdAt)
|
||||||
|
statement.setLong(21, file.updatedAt)
|
||||||
statement.executeUpdate()
|
statement.executeUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -536,6 +539,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
f.storage_uri AS storage_uri,
|
f.storage_uri AS storage_uri,
|
||||||
f.last_seen_at AS last_seen_at,
|
f.last_seen_at AS last_seen_at,
|
||||||
f.reading_status AS reading_status,
|
f.reading_status AS reading_status,
|
||||||
|
f.favorite AS favorite,
|
||||||
f.last_read_at AS last_read_at,
|
f.last_read_at AS last_read_at,
|
||||||
f.imported_at AS imported_at
|
f.imported_at AS imported_at
|
||||||
FROM book_files f
|
FROM book_files f
|
||||||
@ -692,6 +696,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
f.storage_uri AS storage_uri,
|
f.storage_uri AS storage_uri,
|
||||||
f.last_seen_at AS last_seen_at,
|
f.last_seen_at AS last_seen_at,
|
||||||
f.reading_status AS reading_status,
|
f.reading_status AS reading_status,
|
||||||
|
f.favorite AS favorite,
|
||||||
f.last_read_at AS last_read_at,
|
f.last_read_at AS last_read_at,
|
||||||
f.imported_at AS imported_at
|
f.imported_at AS imported_at
|
||||||
FROM book_files f
|
FROM book_files f
|
||||||
@ -732,6 +737,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
f.storage_uri AS storage_uri,
|
f.storage_uri AS storage_uri,
|
||||||
f.last_seen_at AS last_seen_at,
|
f.last_seen_at AS last_seen_at,
|
||||||
f.reading_status AS reading_status,
|
f.reading_status AS reading_status,
|
||||||
|
f.favorite AS favorite,
|
||||||
f.last_read_at AS last_read_at,
|
f.last_read_at AS last_read_at,
|
||||||
f.imported_at AS imported_at
|
f.imported_at AS imported_at
|
||||||
FROM book_files f
|
FROM book_files f
|
||||||
@ -771,6 +777,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
f.storage_uri AS storage_uri,
|
f.storage_uri AS storage_uri,
|
||||||
f.last_seen_at AS last_seen_at,
|
f.last_seen_at AS last_seen_at,
|
||||||
f.reading_status AS reading_status,
|
f.reading_status AS reading_status,
|
||||||
|
f.favorite AS favorite,
|
||||||
f.last_read_at AS last_read_at,
|
f.last_read_at AS last_read_at,
|
||||||
f.imported_at AS imported_at
|
f.imported_at AS imported_at
|
||||||
FROM book_files f
|
FROM book_files f
|
||||||
@ -847,6 +854,24 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateFavorite(id: String, favorite: Boolean): Boolean {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
return connection.prepareStatement(
|
||||||
|
"""
|
||||||
|
UPDATE book_files
|
||||||
|
SET favorite = ?, updated_at = ?
|
||||||
|
WHERE id = ?
|
||||||
|
OR body_cluster_id = (SELECT body_cluster_id FROM book_files WHERE id = ? AND body_cluster_id IS NOT NULL)
|
||||||
|
""".trimIndent()
|
||||||
|
).use { statement ->
|
||||||
|
statement.setBoolean(1, favorite)
|
||||||
|
statement.setLong(2, now)
|
||||||
|
statement.setString(3, id)
|
||||||
|
statement.setString(4, id)
|
||||||
|
statement.executeUpdate() > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun touchLastReadAt(id: String, lastReadAt: Long): Boolean {
|
override fun touchLastReadAt(id: String, lastReadAt: Long): Boolean {
|
||||||
return connection.prepareStatement(
|
return connection.prepareStatement(
|
||||||
"""
|
"""
|
||||||
@ -1085,6 +1110,7 @@ private fun ResultSet.toLibraryFileRecord() = LibraryFileRecord(
|
|||||||
storageUri = getString("storage_uri"),
|
storageUri = getString("storage_uri"),
|
||||||
lastSeenAt = getLongOrNull("last_seen_at"),
|
lastSeenAt = getLongOrNull("last_seen_at"),
|
||||||
readingStatus = getReadingStatus("reading_status"),
|
readingStatus = getReadingStatus("reading_status"),
|
||||||
|
favorite = getBoolean("favorite"),
|
||||||
lastReadAt = getLongOrNull("last_read_at"),
|
lastReadAt = getLongOrNull("last_read_at"),
|
||||||
importedAt = getLong("imported_at"),
|
importedAt = getLong("imported_at"),
|
||||||
)
|
)
|
||||||
@ -1122,6 +1148,7 @@ private fun ResultSet.toBookFileRecord() = BookFileRecord(
|
|||||||
lastModifiedMillis = getLongOrNull("last_modified_millis"),
|
lastModifiedMillis = getLongOrNull("last_modified_millis"),
|
||||||
lastSeenAt = getLongOrNull("last_seen_at"),
|
lastSeenAt = getLongOrNull("last_seen_at"),
|
||||||
readingStatus = getReadingStatus("reading_status"),
|
readingStatus = getReadingStatus("reading_status"),
|
||||||
|
favorite = getBoolean("favorite"),
|
||||||
lastReadAt = getLongOrNull("last_read_at"),
|
lastReadAt = getLongOrNull("last_read_at"),
|
||||||
importedAt = getLong("imported_at"),
|
importedAt = getLong("imported_at"),
|
||||||
createdAt = getLong("created_at"),
|
createdAt = getLong("created_at"),
|
||||||
|
|||||||
@ -122,6 +122,9 @@ class H2LibraryDatabaseTest {
|
|||||||
assertEquals("2024", db.files.getLibraryFile("file-1")?.date)
|
assertEquals("2024", db.files.getLibraryFile("file-1")?.date)
|
||||||
assertEquals("image/jpeg", db.books.getCover("book-1")?.mimeType)
|
assertEquals("image/jpeg", db.books.getCover("book-1")?.mimeType)
|
||||||
assertEquals(BookReadingStatus.NEW, db.files.getLibraryFile("file-1")?.readingStatus)
|
assertEquals(BookReadingStatus.NEW, db.files.getLibraryFile("file-1")?.readingStatus)
|
||||||
|
assertEquals(false, db.files.getLibraryFile("file-1")?.favorite)
|
||||||
|
assertEquals(true, db.files.updateFavorite("file-1", true))
|
||||||
|
assertEquals(true, db.files.getLibraryFile("file-1")?.favorite)
|
||||||
assertEquals(true, db.files.updateReadingStatus("file-1", BookReadingStatus.READING))
|
assertEquals(true, db.files.updateReadingStatus("file-1", BookReadingStatus.READING))
|
||||||
assertEquals(BookReadingStatus.READING, db.files.getLibraryFile("file-1")?.readingStatus)
|
assertEquals(BookReadingStatus.READING, db.files.getLibraryFile("file-1")?.readingStatus)
|
||||||
assertEquals("body-1", db.bodies.findByExactTextHash("text-sha", 1)?.id)
|
assertEquals("body-1", db.bodies.findByExactTextHash("text-sha", 1)?.id)
|
||||||
@ -299,6 +302,9 @@ class H2LibraryDatabaseTest {
|
|||||||
assertEquals(true, db.files.updateReadingStatus("file-zip", BookReadingStatus.READING))
|
assertEquals(true, db.files.updateReadingStatus("file-zip", BookReadingStatus.READING))
|
||||||
assertEquals(BookReadingStatus.READING, db.files.get("file-fb2")?.readingStatus)
|
assertEquals(BookReadingStatus.READING, db.files.get("file-fb2")?.readingStatus)
|
||||||
assertEquals(BookReadingStatus.READING, db.files.get("file-zip")?.readingStatus)
|
assertEquals(BookReadingStatus.READING, db.files.get("file-zip")?.readingStatus)
|
||||||
|
assertEquals(true, db.files.updateFavorite("file-zip", true))
|
||||||
|
assertEquals(true, db.files.get("file-fb2")?.favorite)
|
||||||
|
assertEquals(true, db.files.get("file-zip")?.favorite)
|
||||||
db.close()
|
db.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user