This commit is contained in:
tom5079
2021-12-17 22:34:46 +09:00
parent e7debfec46
commit 98fda1a53f
7 changed files with 155 additions and 89 deletions

View File

@@ -2,6 +2,7 @@ package xyz.quaver.pupil.db
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import xyz.quaver.pupil.sources.ItemInfo
@Entity(primaryKeys = ["source", "itemID"]) @Entity(primaryKeys = ["source", "itemID"])
data class Bookmark( data class Bookmark(
@@ -22,10 +23,17 @@ interface BookmarkDao {
fun contains(source: String, itemID: String): LiveData<Boolean> fun contains(source: String, itemID: String): LiveData<Boolean>
fun contains(bookmark: Bookmark) = contains(bookmark.source, bookmark.itemID) fun contains(bookmark: Bookmark) = contains(bookmark.source, bookmark.itemID)
fun contains(itemInfo: ItemInfo) = contains(itemInfo.source, itemInfo.itemID)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(bookmark: Bookmark) 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 @Delete
suspend fun delete(bookmark: Bookmark) 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))
} }

View File

@@ -35,8 +35,7 @@ data class SearchResultEvent(val type: Type, val itemID: String, val payload: Pa
enum class Type { enum class Type {
OPEN_READER, OPEN_READER,
OPEN_DETAILS, OPEN_DETAILS,
NEW_QUERY, NEW_QUERY
TOGGLE_FAVORITES
} }
} }

View File

@@ -29,6 +29,8 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Female import androidx.compose.material.icons.filled.Female
import androidx.compose.material.icons.filled.Male import androidx.compose.material.icons.filled.Male
import androidx.compose.material.icons.filled.Star 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.material.icons.outlined.StarOutline
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@@ -374,10 +376,12 @@ class Hitomi(app: Application) : Source(), DIAware {
var group by remember { mutableStateOf(emptyList<String>()) } var group by remember { mutableStateOf(emptyList<String>()) }
var pageCount by remember { mutableStateOf("-") } var pageCount by remember { mutableStateOf("-") }
val bookmark by bookmarkDao.contains(itemInfo).observeAsState(false)
LaunchedEffect(itemInfo) { LaunchedEffect(itemInfo) {
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
itemInfo.getPageCount()?.run { itemInfo.getPageCount()?.let {
pageCount = "${this}P" pageCount = "${it}P"
} }
} }
@@ -468,35 +472,32 @@ class Hitomi(app: Application) : Source(), DIAware {
Divider( Divider(
thickness = 1.dp, thickness = 1.dp,
modifier = Modifier.padding(0.dp, 8.dp) modifier = Modifier.padding(0.dp, 8.dp, 0.dp, 0.dp)
) )
Box( Row(
Modifier modifier = Modifier
.fillMaxWidth() .padding(8.dp)
.wrapContentHeight() .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text( Text(itemInfo.itemID)
itemInfo.itemID,
color = MaterialTheme.colors.onSurface,
modifier = Modifier
.padding(8.dp)
.align(Alignment.CenterStart)
)
Text( Text(pageCount)
pageCount,
color = MaterialTheme.colors.onSurface,
modifier = Modifier.align(Alignment.Center)
)
Image( Icon(
painterResource(id = R.drawable.ic_star_empty), if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
contentDescription = "Favorite", contentDescription = null,
tint = Orange500,
modifier = Modifier modifier = Modifier
.size(32.dp) .size(32.dp)
.padding(4.dp) .clickable {
.align(Alignment.CenterEnd) CoroutineScope(Dispatchers.IO).launch {
if (bookmark) bookmarkDao.delete(itemInfo)
else bookmarkDao.insert(itemInfo)
}
}
) )
} }
} }

View File

@@ -30,6 +30,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Female import androidx.compose.material.icons.filled.Female
import androidx.compose.material.icons.filled.Male import androidx.compose.material.icons.filled.Male
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.StarOutline
import androidx.compose.material.icons.outlined.StarOutline import androidx.compose.material.icons.outlined.StarOutline
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState 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) { override fun SearchResult(itemInfo: ItemInfo, onEvent: (SearchResultEvent) -> Unit) {
itemInfo as HiyobiItemInfo itemInfo as HiyobiItemInfo
val bookmark by bookmarkDao.contains(itemInfo).observeAsState(false)
val painter = rememberImagePainter(itemInfo.thumbnail) val painter = rememberImagePainter(itemInfo.thumbnail)
Row( Column(
modifier = Modifier.clickable { modifier = Modifier.clickable {
onEvent(SearchResultEvent(SearchResultEvent.Type.OPEN_READER, itemInfo.itemID, itemInfo)) onEvent(SearchResultEvent(SearchResultEvent.Type.OPEN_READER, itemInfo.itemID, itemInfo))
} }
) { ) {
Image( Row {
painter = painter, Image(
contentDescription = null, painter = painter,
modifier = Modifier contentDescription = null,
.requiredWidth(150.dp) modifier = Modifier
.aspectRatio( .requiredWidth(150.dp)
with(painter.intrinsicSize) { if (this == Size.Unspecified) 1f else width / height }, .aspectRatio(
true with(painter.intrinsicSize) { if (this == Size.Unspecified) 1f else width / height },
) true
.padding(0.dp, 0.dp, 8.dp, 0.dp) )
.align(Alignment.CenterVertically), .padding(0.dp, 0.dp, 8.dp, 0.dp)
contentScale = ContentScale.FillWidth .align(Alignment.CenterVertically),
) contentScale = ContentScale.FillWidth
Column {
Text(
itemInfo.title,
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onSurface
) )
val artistStringBuilder = StringBuilder() Column {
with (itemInfo.artists) {
if (this.isNotEmpty())
artistStringBuilder.append(this.joinToString(", ") { it.wordCapitalize() })
}
if (artistStringBuilder.isNotEmpty())
Text( Text(
artistStringBuilder.toString(), itemInfo.title,
style = MaterialTheme.typography.subtitle1, style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) 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( Text(
stringResource( stringResource(id = R.string.galleryblock_type, itemInfo.type),
id = R.string.galleryblock_series,
itemInfo.series.joinToString { it.wordCapitalize() }
),
style = MaterialTheme.typography.body2, style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F)
) )
Text( key(itemInfo.tags) {
stringResource(id = R.string.galleryblock_type, itemInfo.type), TagGroup(tags = itemInfo.tags)
style = MaterialTheme.typography.body2, }
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F)
)
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)
}
}
)
}
} }
} }

View File

@@ -24,10 +24,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn 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.Icons
import androidx.compose.material.icons.filled.BrokenImage import androidx.compose.material.icons.filled.BrokenImage
import androidx.compose.material.icons.filled.Fullscreen 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.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat 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.FloatingActionButtonState
import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton
import xyz.quaver.pupil.ui.composable.SubFabItem 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.theme.PupilTheme
import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel
import xyz.quaver.pupil.util.FileXImageSource import xyz.quaver.pupil.util.FileXImageSource
@@ -83,6 +85,7 @@ class ReaderActivity : ComponentActivity(), DIAware {
var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) } var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) }
val imageSources = remember { mutableStateListOf<ImageSource?>() } val imageSources = remember { mutableStateListOf<ImageSource?>() }
val states = remember { mutableStateListOf<SubSampledImageState>() } val states = remember { mutableStateListOf<SubSampledImageState>() }
val bookmark by model.bookmark.observeAsState(false)
val scaffoldState = rememberScaffoldState() val scaffoldState = rememberScaffoldState()
val snackbarCoroutineScope = rememberCoroutineScope() val snackbarCoroutineScope = rememberCoroutineScope()
@@ -130,16 +133,32 @@ class ReaderActivity : ComponentActivity(), DIAware {
title = { title = {
Text( Text(
model.title ?: stringResource(R.string.reader_loading), model.title ?: stringResource(R.string.reader_loading),
color = MaterialTheme.colors.onSecondary color = MaterialTheme.colors.onSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
) )
}, },
actions = { actions = {
model.sourceIcon?.let { sourceIcon -> Row(
Image( modifier = Modifier.padding(16.dp, 0.dp),
modifier = Modifier.size(36.dp), verticalAlignment = Alignment.CenterVertically,
painter = painterResource(id = sourceIcon), horizontalArrangement = Arrangement.spacedBy(24.dp)
contentDescription = null ) {
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
)
}
} }
} }
) )

View File

@@ -132,7 +132,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
for (result in channel) { for (result in channel) {
yield() yield()
searchResults.add(result) searchResults.add(result)
logger.info { result.toString() }
} }
loading = false loading = false

View File

@@ -43,6 +43,7 @@ import xyz.quaver.pupil.sources.ItemInfo
import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.util.NetworkCache import xyz.quaver.pupil.util.NetworkCache
import xyz.quaver.pupil.util.source import xyz.quaver.pupil.util.source
import java.nio.file.Files.delete
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { 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 historyDao = database.historyDao()
private val bookmarkDao = database.bookmarkDao() private val bookmarkDao = database.bookmarkDao()
lateinit var bookmark: LiveData<Boolean>
private set
var error by mutableStateOf(false) var error by mutableStateOf(false)
private set private set
@@ -108,6 +112,8 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware {
itemID = intent.getStringExtra("id") ?: error("Invalid itemID") itemID = intent.getStringExtra("id") ?: error("Invalid itemID")
title = intent.getParcelableExtra<ItemInfo>("payload")?.title title = intent.getParcelableExtra<ItemInfo>("payload")?.title
} }
bookmark = bookmarkDao.contains(source!!.name, itemID!!)
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -203,14 +209,14 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware {
} }
fun toggleBookmark() { fun toggleBookmark() {
val bookmark = source?.let { source -> itemID?.let { itemID -> Bookmark(source.name, itemID) } } ?: return source?.name?.let { source ->
itemID?.let { itemID ->
CoroutineScope(Dispatchers.IO).launch { bookmark.value?.let { bookmark ->
if (bookmarkDao.contains(bookmark).value ?: return@launch) CoroutineScope(Dispatchers.IO).launch {
bookmarkDao.delete(bookmark) if (bookmark) bookmarkDao.delete(source, itemID)
else else bookmarkDao.insert(source, itemID)
bookmarkDao.insert(bookmark) }
} } } }
} }
override fun onCleared() { override fun onCleared() {