Bookmark
This commit is contained in:
@@ -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<Boolean>
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String>()) }
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ImageSource?>() }
|
||||
val states = remember { mutableStateListOf<SubSampledImageState>() }
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Boolean>
|
||||
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<ItemInfo>("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() {
|
||||
|
||||
Reference in New Issue
Block a user