From 98fda1a53f6c9b192ba509330df00f89e9310f92 Mon Sep 17 00:00:00 2001 From: tom5079 Date: Fri, 17 Dec 2021 22:34:46 +0900 Subject: [PATCH] Bookmark --- .../main/java/xyz/quaver/pupil/db/Bookmark.kt | 8 ++ .../java/xyz/quaver/pupil/sources/Common.kt | 3 +- .../java/xyz/quaver/pupil/sources/Hitomi.kt | 49 +++---- .../xyz/quaver/pupil/sources/Hiyobi_io.kt | 122 +++++++++++------- .../xyz/quaver/pupil/ui/ReaderActivity.kt | 39 ++++-- .../pupil/ui/viewmodel/MainViewModel.kt | 1 - .../pupil/ui/viewmodel/ReaderViewModel.kt | 22 ++-- 7 files changed, 155 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/xyz/quaver/pupil/db/Bookmark.kt b/app/src/main/java/xyz/quaver/pupil/db/Bookmark.kt index fcc48ab9..d3b9b1ae 100644 --- a/app/src/main/java/xyz/quaver/pupil/db/Bookmark.kt +++ b/app/src/main/java/xyz/quaver/pupil/db/Bookmark.kt @@ -2,6 +2,7 @@ package xyz.quaver.pupil.db import androidx.lifecycle.LiveData import androidx.room.* +import xyz.quaver.pupil.sources.ItemInfo @Entity(primaryKeys = ["source", "itemID"]) data class Bookmark( @@ -22,10 +23,17 @@ interface BookmarkDao { fun contains(source: String, itemID: String): LiveData fun contains(bookmark: Bookmark) = contains(bookmark.source, bookmark.itemID) + fun contains(itemInfo: ItemInfo) = contains(itemInfo.source, itemInfo.itemID) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(bookmark: Bookmark) + suspend fun insert(source: String, itemID: String) = insert(Bookmark(source, itemID)) + suspend fun insert(itemInfo: ItemInfo) = insert(Bookmark(itemInfo.source, itemInfo.itemID)) + @Delete suspend fun delete(bookmark: Bookmark) + + suspend fun delete(source: String, itemID: String) = delete(Bookmark(source, itemID)) + suspend fun delete(itemInfo: ItemInfo) = delete(Bookmark(itemInfo.source, itemInfo.itemID)) } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt index 2344c10a..620693cf 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt @@ -35,8 +35,7 @@ data class SearchResultEvent(val type: Type, val itemID: String, val payload: Pa enum class Type { OPEN_READER, OPEN_DETAILS, - NEW_QUERY, - TOGGLE_FAVORITES + NEW_QUERY } } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt index e56856ea..6f64556b 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt @@ -29,6 +29,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Female import androidx.compose.material.icons.filled.Male import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.StarOutline +import androidx.compose.material.icons.outlined.Star import androidx.compose.material.icons.outlined.StarOutline import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -374,10 +376,12 @@ class Hitomi(app: Application) : Source(), DIAware { var group by remember { mutableStateOf(emptyList()) } var pageCount by remember { mutableStateOf("-") } + val bookmark by bookmarkDao.contains(itemInfo).observeAsState(false) + LaunchedEffect(itemInfo) { launch(Dispatchers.Default) { - itemInfo.getPageCount()?.run { - pageCount = "${this}P" + itemInfo.getPageCount()?.let { + pageCount = "${it}P" } } @@ -468,35 +472,32 @@ class Hitomi(app: Application) : Source(), DIAware { Divider( thickness = 1.dp, - modifier = Modifier.padding(0.dp, 8.dp) + modifier = Modifier.padding(0.dp, 8.dp, 0.dp, 0.dp) ) - Box( - Modifier - .fillMaxWidth() - .wrapContentHeight() + Row( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - Text( - itemInfo.itemID, - color = MaterialTheme.colors.onSurface, - modifier = Modifier - .padding(8.dp) - .align(Alignment.CenterStart) - ) + Text(itemInfo.itemID) - Text( - pageCount, - color = MaterialTheme.colors.onSurface, - modifier = Modifier.align(Alignment.Center) - ) + Text(pageCount) - Image( - painterResource(id = R.drawable.ic_star_empty), - contentDescription = "Favorite", + Icon( + if (bookmark) Icons.Default.Star else Icons.Default.StarOutline, + contentDescription = null, + tint = Orange500, modifier = Modifier .size(32.dp) - .padding(4.dp) - .align(Alignment.CenterEnd) + .clickable { + CoroutineScope(Dispatchers.IO).launch { + if (bookmark) bookmarkDao.delete(itemInfo) + else bookmarkDao.insert(itemInfo) + } + } ) } } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt b/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt index 0d9fb3ef..9eefef78 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt @@ -30,6 +30,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Female import androidx.compose.material.icons.filled.Male import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.StarOutline import androidx.compose.material.icons.outlined.StarOutline import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -329,68 +330,101 @@ class Hiyobi_io(app: Application): Source(), DIAware { override fun SearchResult(itemInfo: ItemInfo, onEvent: (SearchResultEvent) -> Unit) { itemInfo as HiyobiItemInfo + val bookmark by bookmarkDao.contains(itemInfo).observeAsState(false) + val painter = rememberImagePainter(itemInfo.thumbnail) - Row( + Column( modifier = Modifier.clickable { onEvent(SearchResultEvent(SearchResultEvent.Type.OPEN_READER, itemInfo.itemID, itemInfo)) } ) { - Image( - painter = painter, - contentDescription = null, - modifier = Modifier - .requiredWidth(150.dp) - .aspectRatio( - with(painter.intrinsicSize) { if (this == Size.Unspecified) 1f else width / height }, - true - ) - .padding(0.dp, 0.dp, 8.dp, 0.dp) - .align(Alignment.CenterVertically), - contentScale = ContentScale.FillWidth - ) - - Column { - Text( - itemInfo.title, - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface + Row { + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .requiredWidth(150.dp) + .aspectRatio( + with(painter.intrinsicSize) { if (this == Size.Unspecified) 1f else width / height }, + true + ) + .padding(0.dp, 0.dp, 8.dp, 0.dp) + .align(Alignment.CenterVertically), + contentScale = ContentScale.FillWidth ) - val artistStringBuilder = StringBuilder() - - with (itemInfo.artists) { - if (this.isNotEmpty()) - artistStringBuilder.append(this.joinToString(", ") { it.wordCapitalize() }) - } - - if (artistStringBuilder.isNotEmpty()) + Column { Text( - artistStringBuilder.toString(), - style = MaterialTheme.typography.subtitle1, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) + itemInfo.title, + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface ) - if (itemInfo.series.isNotEmpty()) + val artistStringBuilder = StringBuilder() + + with(itemInfo.artists) { + if (this.isNotEmpty()) + artistStringBuilder.append(this.joinToString(", ") { it.wordCapitalize() }) + } + + if (artistStringBuilder.isNotEmpty()) + Text( + artistStringBuilder.toString(), + style = MaterialTheme.typography.subtitle1, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) + ) + + if (itemInfo.series.isNotEmpty()) + Text( + stringResource( + id = R.string.galleryblock_series, + itemInfo.series.joinToString { it.wordCapitalize() } + ), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) + ) + Text( - stringResource( - id = R.string.galleryblock_series, - itemInfo.series.joinToString { it.wordCapitalize() } - ), + stringResource(id = R.string.galleryblock_type, itemInfo.type), style = MaterialTheme.typography.body2, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) ) - Text( - stringResource(id = R.string.galleryblock_type, itemInfo.type), - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) - ) - - key(itemInfo.tags) { - TagGroup(tags = itemInfo.tags) + key(itemInfo.tags) { + TagGroup(tags = itemInfo.tags) + } } } + + Divider( + thickness = 1.dp, + modifier = Modifier.padding(0.dp, 8.dp, 0.dp, 0.dp) + ) + + Row( + modifier = Modifier.padding(8.dp).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(itemInfo.itemID) + + Text("${itemInfo.pageCount}P") + + Icon( + if (bookmark) Icons.Default.Star else Icons.Default.StarOutline, + contentDescription = null, + tint = Orange500, + modifier = Modifier + .size(32.dp) + .clickable { + CoroutineScope(Dispatchers.IO).launch { + if (bookmark) bookmarkDao.delete(itemInfo) + else bookmarkDao.insert(itemInfo) + } + } + ) + } } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index 5ada3b63..887bd48e 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -24,10 +24,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.border -import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.* import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -36,7 +33,10 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BrokenImage import androidx.compose.material.icons.filled.Fullscreen +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.StarOutline import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -45,6 +45,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat @@ -60,6 +61,7 @@ import xyz.quaver.pupil.R import xyz.quaver.pupil.ui.composable.FloatingActionButtonState import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton import xyz.quaver.pupil.ui.composable.SubFabItem +import xyz.quaver.pupil.ui.theme.Orange500 import xyz.quaver.pupil.ui.theme.PupilTheme import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel import xyz.quaver.pupil.util.FileXImageSource @@ -83,6 +85,7 @@ class ReaderActivity : ComponentActivity(), DIAware { var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) } val imageSources = remember { mutableStateListOf() } val states = remember { mutableStateListOf() } + val bookmark by model.bookmark.observeAsState(false) val scaffoldState = rememberScaffoldState() val snackbarCoroutineScope = rememberCoroutineScope() @@ -130,16 +133,32 @@ class ReaderActivity : ComponentActivity(), DIAware { title = { Text( model.title ?: stringResource(R.string.reader_loading), - color = MaterialTheme.colors.onSecondary + color = MaterialTheme.colors.onSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) }, actions = { - model.sourceIcon?.let { sourceIcon -> - Image( - modifier = Modifier.size(36.dp), - painter = painterResource(id = sourceIcon), - contentDescription = null + Row( + modifier = Modifier.padding(16.dp, 0.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + Icon( + if (bookmark) Icons.Default.Star else Icons.Default.StarOutline, + contentDescription = null, + tint = Orange500, + modifier = Modifier.size(24.dp).clickable { + model.toggleBookmark() + } ) + model.sourceIcon?.let { sourceIcon -> + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = sourceIcon), + contentDescription = null + ) + } } } ) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt index 19766229..27db167b 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt @@ -132,7 +132,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { for (result in channel) { yield() searchResults.add(result) - logger.info { result.toString() } } loading = false diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt index a4dc45f8..a80c3622 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt @@ -43,6 +43,7 @@ import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.util.NetworkCache import xyz.quaver.pupil.util.source +import java.nio.file.Files.delete @Suppress("UNCHECKED_CAST") class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { @@ -60,6 +61,9 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { private val historyDao = database.historyDao() private val bookmarkDao = database.bookmarkDao() + lateinit var bookmark: LiveData + private set + var error by mutableStateOf(false) private set @@ -108,6 +112,8 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { itemID = intent.getStringExtra("id") ?: error("Invalid itemID") title = intent.getParcelableExtra("payload")?.title } + + bookmark = bookmarkDao.contains(source!!.name, itemID!!) } @OptIn(ExperimentalCoroutinesApi::class) @@ -203,14 +209,14 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { } fun toggleBookmark() { - val bookmark = source?.let { source -> itemID?.let { itemID -> Bookmark(source.name, itemID) } } ?: return - - CoroutineScope(Dispatchers.IO).launch { - if (bookmarkDao.contains(bookmark).value ?: return@launch) - bookmarkDao.delete(bookmark) - else - bookmarkDao.insert(bookmark) - } + source?.name?.let { source -> + itemID?.let { itemID -> + bookmark.value?.let { bookmark -> + CoroutineScope(Dispatchers.IO).launch { + if (bookmark) bookmarkDao.delete(source, itemID) + else bookmarkDao.insert(source, itemID) + } + } } } } override fun onCleared() {