Support display tag vote status (#1965)
* Support parsing GalleryTag vote status * Append utf-8 emoji vote status * Refresh tag list after voting * Refine to use substringAfter * Fix implicitNulls * Refine to use withDraw vote * Recompose first * Optimize Snackbar to be cancellable for freq action * Fix GalleryDetail recompose jump * Refine VoteStatus display * Support parse WeakStatus Active * Append instead * Add switch * Refine naming * Fix fillInfo * Drop correctly * Refine * Refine rememberInVM impl with key * Be type safe * Refine --------- Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
1 parent
03a343784d
commit
99d98b6227
18 files changed
+137
-83
No files matched your search
@@ -131,6 +131,7 @@ object Settings : DataStorePreferences(null) {
|
||||
val avatar = stringOrNullPref("avatar", null)
|
||||
val recentDownloadLabel = stringOrNullPref("recent_download_label", null)
|
||||
|
||||
var showVoteStatus by boolPref("show_vote_status", false)
|
||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
||||
var downloadAuthority by stringOrNullPref("image_authority", null)
|
||||
var downloadPath by stringOrNullPref("image_path", null)
|
||||
|
||||
@@ -50,7 +50,10 @@ suspend inline fun <reified T> HttpStatement.executeAndParseAs() = executeSafely
|
||||
inline fun <reified T> BufferedSource.parseAs(): T = json.decodeFromBufferedSource(this)
|
||||
inline fun <reified T> String.parseAs(): T = json.decodeFromString(this)
|
||||
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
fun HttpRequestBuilder.applyEhConfig(referer: String?, origin: String?) {
|
||||
header("Referer", referer)
|
||||
|
||||
@@ -59,6 +59,8 @@ data class GalleryDetail(
|
||||
fun fillInfo() {
|
||||
val index = LANGUAGES.indexOf(language)
|
||||
if (index != -1) simpleLanguage = S_LANGS[index]
|
||||
simpleTags = tagGroups.fastFlatMap(GalleryTagGroup::tags)
|
||||
simpleTags = tagGroups.fastFlatMap(GalleryTagGroup::tags).map { (text, power, _) ->
|
||||
if (power == PowerStatus.WEAK) "_$text" else text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
enum class VoteStatus(val append: String) {
|
||||
NONE(""),
|
||||
UP("↑"),
|
||||
DOWN("↓"),
|
||||
}
|
||||
|
||||
enum class PowerStatus {
|
||||
SOLID,
|
||||
ACTIVE,
|
||||
WEAK,
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class GalleryTag(
|
||||
val text: String,
|
||||
val power: PowerStatus,
|
||||
val vote: VoteStatus,
|
||||
) : Parcelable
|
||||
@@ -21,5 +21,5 @@ import kotlinx.parcelize.Parcelize
|
||||
@Parcelize
|
||||
data class GalleryTagGroup(
|
||||
val nameSpace: TagNamespace,
|
||||
val tags: List<String>,
|
||||
val tags: List<GalleryTag>,
|
||||
) : Parcelable
|
||||
@@ -28,10 +28,13 @@ import com.hippo.ehviewer.client.data.GalleryCommentList
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.hippo.ehviewer.client.data.GalleryTag
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.data.PowerStatus
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.V1GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.V2GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.VoteStatus
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.OffensiveException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
@@ -280,12 +283,20 @@ object GalleryDetailParser {
|
||||
}
|
||||
checkNotNull(nameSpace) { "$nameSpace is not a valid tag namespace!" }
|
||||
val tags = element.child(1).children().map { e ->
|
||||
val text = e.text()
|
||||
// Sometimes parody tag is followed with '|' and english translate, just remove them
|
||||
val index = text.indexOf('|')
|
||||
val tag = if (index >= 0) text.substring(0, index).trim() else text
|
||||
// weak tag
|
||||
if (e.className() == "gtw") "_$tag" else tag
|
||||
GalleryTag(
|
||||
// Sometimes parody tag is followed with '|' and english translate, just remove them
|
||||
text = e.text().substringBefore('|').trim(),
|
||||
power = when {
|
||||
e.hasClass("gtw") -> PowerStatus.WEAK
|
||||
e.hasClass("gtl") -> PowerStatus.ACTIVE
|
||||
else -> PowerStatus.SOLID
|
||||
},
|
||||
vote = when {
|
||||
e.child(0).hasClass("tup") -> VoteStatus.UP
|
||||
e.child(0).hasClass("tdn") -> VoteStatus.DOWN
|
||||
else -> VoteStatus.NONE
|
||||
},
|
||||
)
|
||||
}
|
||||
check(tags.isNotEmpty()) { "TagGroup is empty!" }
|
||||
GalleryTagGroup(nameSpace, tags)
|
||||
@@ -297,7 +308,7 @@ object GalleryDetailParser {
|
||||
/**
|
||||
* Parse tag groups with html parser
|
||||
*/
|
||||
private fun parseTagGroups(document: Document): List<GalleryTagGroup> = Either.catch {
|
||||
fun parseTagGroups(document: Document): List<GalleryTagGroup> = Either.catch {
|
||||
val taglist = document.getElementById("taglist")!!
|
||||
taglist.child(0).child(0).children().mapNotNull(::parseSingleTagGroup)
|
||||
}.getOrElse {
|
||||
|
||||
@@ -36,7 +36,7 @@ object GalleryTokenApiParser {
|
||||
fun parse(body: String): String = Either.catch {
|
||||
body.parseAs<Result>().tokenList[0].token
|
||||
}.getOrElse {
|
||||
throw EhException(body.parseAs<Error>().error)
|
||||
throw EhException(body.parseAs<VoteTagResult>().error)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -17,9 +17,15 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.parseAs
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object VoteTagParser {
|
||||
// {"error":"The tag \"neko\" is not allowed. Use character:neko or artist:neko"}
|
||||
fun parse(body: String): String? = runCatching { body.parseAs<Error>().error }.getOrNull()
|
||||
fun parse(body: String): List<GalleryTagGroup> = body.parseAs<VoteTagResult>().let { (error, tagpane) ->
|
||||
error?.let { error(error) }
|
||||
checkNotNull(tagpane)
|
||||
GalleryDetailParser.parseTagGroups(Jsoup.parse("<div id=\"taglist\">$tagpane</div>"))
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -3,4 +3,7 @@ package com.hippo.ehviewer.client.parser
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Error(val error: String)
|
||||
data class VoteTagResult(
|
||||
val error: String?,
|
||||
val tagpane: String?,
|
||||
)
|
||||
@@ -3,6 +3,8 @@ package com.hippo.ehviewer.spider
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryTag
|
||||
import com.hippo.ehviewer.client.data.PowerStatus
|
||||
import com.hippo.ehviewer.client.data.SimpleTagsConverter
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Artist
|
||||
@@ -52,7 +54,7 @@ fun GalleryInfo.getComicInfo(): ComicInfo {
|
||||
with(TagNamespace) {
|
||||
when (this@getComicInfo) {
|
||||
is GalleryDetail -> tagGroups.forEach { group ->
|
||||
val list = group.tags.filterNot { it == TAG_ORIGINAL || it.startsWith('_') }
|
||||
val list = group.tags.filterNot { (text, power, _) -> text == TAG_ORIGINAL || power == PowerStatus.WEAK }.map(GalleryTag::text)
|
||||
when (val ns = group.nameSpace) {
|
||||
Artist, Cosplayer -> artists.addAll(list)
|
||||
Group -> groups.addAll(list)
|
||||
|
||||
@@ -548,13 +548,14 @@ class MainActivity : EhActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, useToast: Boolean = false) {
|
||||
val message = getString(id)
|
||||
fun showTip(message: String, useToast: Boolean = false) {
|
||||
if (useToast || !tipFlow.tryEmit(message)) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, useToast: Boolean = false) = showTip(getString(id), useToast)
|
||||
|
||||
override fun onProvideAssistContent(outContent: AssistContent?) {
|
||||
super.onProvideAssistContent(outContent)
|
||||
shareUrl?.let { outContent?.webUri = Uri.parse(shareUrl) }
|
||||
|
||||
@@ -20,14 +20,16 @@ import androidx.compose.ui.unit.dp
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.data.PowerStatus
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.VoteStatus
|
||||
import com.hippo.ehviewer.ui.tools.includeFontPadding
|
||||
|
||||
@Composable
|
||||
fun GalleryTags(
|
||||
tagGroups: List<GalleryTagGroup>,
|
||||
onTagClick: (String) -> Unit,
|
||||
onTagLongClick: (String, String) -> Unit,
|
||||
onTagLongClick: (String, String, VoteStatus) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -36,28 +38,25 @@ fun GalleryTags(
|
||||
fun TagNamespace.translate() = ehTags?.getTranslation(tag = value) ?: value
|
||||
fun String.translate(ns: TagNamespace) = ehTags?.getTranslation(prefix = ns.prefix, tag = this) ?: this
|
||||
Column(modifier) {
|
||||
tagGroups.forEach { tagGroup ->
|
||||
tagGroups.forEach { (ns, tags) ->
|
||||
Row {
|
||||
val ns = tagGroup.nameSpace
|
||||
BaseRoundText(
|
||||
text = ns.translate(),
|
||||
isGroup = true,
|
||||
)
|
||||
FlowRow {
|
||||
tagGroup.tags.forEach {
|
||||
val weak = it.startsWith('_')
|
||||
val real = it.removePrefix("_")
|
||||
val translated = real.translate(ns)
|
||||
val tag = tagGroup.nameSpace.value + ":" + real
|
||||
tags.forEach { (text, power, vote) ->
|
||||
val translation = text.translate(ns)
|
||||
val tag = ns.value + ":" + text
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
BaseRoundText(
|
||||
text = translated,
|
||||
weak = weak,
|
||||
text = if (Settings.showVoteStatus) translation + vote.append else translation,
|
||||
weak = power == PowerStatus.WEAK,
|
||||
modifier = Modifier.combinedClickable(
|
||||
onClick = { onTagClick(tag) },
|
||||
onLongClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onTagLongClick(translated, tag)
|
||||
onTagLongClick(tag, translation, vote)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -83,6 +83,7 @@ import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.V2GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.VoteStatus
|
||||
import com.hippo.ehviewer.client.data.asGalleryDetail
|
||||
import com.hippo.ehviewer.client.data.findBaseInfo
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
@@ -149,7 +150,6 @@ import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import moe.tarsin.coroutines.runSwallowingWithUI
|
||||
@@ -161,6 +161,7 @@ fun GalleryDetailContent(
|
||||
contentPadding: PaddingValues,
|
||||
getDetailError: String,
|
||||
onRetry: () -> Unit,
|
||||
voteTag: VoteTag,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val keylineMargin = dimensionResource(R.dimen.keyline_margin)
|
||||
@@ -245,7 +246,7 @@ fun GalleryDetailContent(
|
||||
}
|
||||
}
|
||||
|
||||
val previews = galleryDetail.collectPreviewItems()
|
||||
val previews = galleryDetail?.collectPreviewItems()
|
||||
when (windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.MEDIUM, WindowWidthSizeClass.COMPACT -> FastScrollLazyVerticalGrid(
|
||||
columns = GridCells.Fixed(thumbColumns),
|
||||
@@ -292,7 +293,7 @@ fun GalleryDetailContent(
|
||||
if (getDetailError.isNotBlank()) {
|
||||
GalleryDetailErrorTip(error = getDetailError, onClick = onRetry)
|
||||
} else if (galleryDetail != null) {
|
||||
BelowHeader(galleryDetail)
|
||||
BelowHeader(galleryDetail, voteTag)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(keylineMargin),
|
||||
@@ -303,7 +304,7 @@ fun GalleryDetailContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (galleryDetail != null) {
|
||||
if (galleryDetail != null && previews != null) {
|
||||
galleryPreview(galleryDetail, previews) { navToReader(galleryDetail.galleryInfo, it) }
|
||||
}
|
||||
}
|
||||
@@ -360,7 +361,7 @@ fun GalleryDetailContent(
|
||||
if (getDetailError.isNotBlank()) {
|
||||
GalleryDetailErrorTip(error = getDetailError, onClick = onRetry)
|
||||
} else if (galleryDetail != null) {
|
||||
BelowHeader(galleryDetail)
|
||||
BelowHeader(galleryDetail, voteTag)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(keylineMargin),
|
||||
@@ -371,7 +372,7 @@ fun GalleryDetailContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (galleryDetail != null) {
|
||||
if (galleryDetail != null && previews != null) {
|
||||
galleryPreview(galleryDetail, previews) { navToReader(galleryDetail.galleryInfo, it) }
|
||||
}
|
||||
}
|
||||
@@ -380,7 +381,7 @@ fun GalleryDetailContent(
|
||||
|
||||
context(Context, CoroutineScope, DestinationsNavigator, DialogState, SnackbarHostState)
|
||||
@Composable
|
||||
fun BelowHeader(galleryDetail: GalleryDetail) {
|
||||
fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) {
|
||||
@Composable
|
||||
fun EhIconButton(
|
||||
icon: ImageVector,
|
||||
@@ -505,7 +506,7 @@ fun BelowHeader(galleryDetail: GalleryDetail) {
|
||||
text = stringResource(id = R.string.similar_gallery),
|
||||
onClick = {
|
||||
val keyword = EhUtils.extractTitle(galleryDetail.title)
|
||||
val artistTag = galleryDetail.tagGroups.getArtistTag()
|
||||
val artistTag = galleryDetail.tagGroups.artistTag()
|
||||
if (null != keyword) {
|
||||
navigate(
|
||||
ListUrlBuilder(
|
||||
@@ -702,30 +703,26 @@ fun BelowHeader(galleryDetail: GalleryDetail) {
|
||||
val filterAdded = stringResource(R.string.filter_added)
|
||||
val upTag = stringResource(R.string.tag_vote_up)
|
||||
val downTag = stringResource(R.string.tag_vote_down)
|
||||
val withDraw = stringResource(R.string.tag_vote_withdraw)
|
||||
GalleryTags(
|
||||
tagGroups = tags,
|
||||
onTagClick = {
|
||||
navigate(ListUrlBuilder(mode = ListUrlBuilder.MODE_TAG, mKeyword = it).asDst())
|
||||
},
|
||||
onTagLongClick = { translated, tag ->
|
||||
val index = tag.indexOf(':')
|
||||
val temp = if (index >= 0) {
|
||||
tag.substring(index + 1)
|
||||
} else {
|
||||
tag
|
||||
}
|
||||
onTagLongClick = { tag, translation, vote ->
|
||||
val rawValue = tag.substringAfter(':')
|
||||
launchIO {
|
||||
awaitSelectAction {
|
||||
onSelect(copy) {
|
||||
addTextToClipboard(tag)
|
||||
}
|
||||
if (temp != translated) {
|
||||
if (rawValue != translation) {
|
||||
onSelect(copyTrans) {
|
||||
addTextToClipboard(translated)
|
||||
addTextToClipboard(translation)
|
||||
}
|
||||
}
|
||||
onSelect(showDefine) {
|
||||
openBrowser(EhUrl.getTagDefinitionUrl(temp))
|
||||
openBrowser(EhUrl.getTagDefinitionUrl(rawValue))
|
||||
}
|
||||
onSelect(addFilter) {
|
||||
awaitConfirmationOrCancel { Text(text = stringResource(R.string.filter_the_tag, tag)) }
|
||||
@@ -733,8 +730,14 @@ fun BelowHeader(galleryDetail: GalleryDetail) {
|
||||
showSnackbar(filterAdded)
|
||||
}
|
||||
if (galleryDetail.apiUid >= 0) {
|
||||
onSelect(upTag) { galleryDetail.voteTag(tag, 1) }
|
||||
onSelect(downTag) { galleryDetail.voteTag(tag, -1) }
|
||||
when (vote) {
|
||||
VoteStatus.NONE -> {
|
||||
onSelect(upTag) { galleryDetail.voteTag(tag, 1) }
|
||||
onSelect(downTag) { galleryDetail.voteTag(tag, -1) }
|
||||
}
|
||||
VoteStatus.UP -> onSelect(withDraw) { galleryDetail.voteTag(tag, -1) }
|
||||
VoteStatus.DOWN -> onSelect(withDraw) { galleryDetail.voteTag(tag, 1) }
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -764,23 +767,14 @@ private fun getRatingText(rating: Float): Int = when ((rating * 2).roundToInt())
|
||||
else -> R.string.rating_none
|
||||
}
|
||||
|
||||
private fun List<GalleryTagGroup>.getArtistTag(): String? {
|
||||
for (tagGroup in this) {
|
||||
val namespace = tagGroup.nameSpace
|
||||
if (namespace == TagNamespace.Artist || namespace == TagNamespace.Cosplayer) {
|
||||
return "$namespace:${tagGroup.tags[0].removePrefix("_")}"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
private fun List<GalleryTagGroup>.artistTag() = find { (ns, _) -> ns == TagNamespace.Artist || ns == TagNamespace.Cosplayer }?.let { (ns, tags) -> "$ns:${tags[0].text}" }
|
||||
|
||||
context(Context)
|
||||
@Composable
|
||||
private fun GalleryDetail?.collectPreviewItems() = rememberInVM(this) {
|
||||
val detail = this@collectPreviewItems ?: return@rememberInVM emptyFlow()
|
||||
val pageSize = detail.previewList.size
|
||||
val pages = detail.pages
|
||||
val previewPagesMap = detail.previewList.associateBy { it.position } as MutableMap
|
||||
private fun GalleryDetail.collectPreviewItems() = rememberInVM(previewList) {
|
||||
val pageSize = previewList.size
|
||||
val pages = pages
|
||||
val previewPagesMap = previewList.associateBy { it.position } as MutableMap
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = pageSize,
|
||||
@@ -795,7 +789,7 @@ private fun GalleryDetail?.collectPreviewItems() = rememberInVM(this) {
|
||||
val key = params.key ?: 0
|
||||
val up = getOffset(params, key, pages)
|
||||
val end = (up + getLimit(params, key) - 1).coerceAtMost(pages - 1)
|
||||
detail.runSuspendCatching {
|
||||
runSuspendCatching {
|
||||
(up..end).filterNot { it in previewPagesMap }.map { it / pageSize }.toSet()
|
||||
.parMap(concurrency = Settings.multiThreadDownload) { page ->
|
||||
val url = EhUrl.getGalleryDetailUrl(gid, token, page, false)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.hippo.ehviewer.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||
import androidx.collection.SieveCache
|
||||
@@ -18,7 +17,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
@@ -75,6 +73,8 @@ import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
typealias VoteTag = suspend GalleryDetail.(String, Int) -> Unit
|
||||
|
||||
val detailCache = SieveCache<Long, GalleryDetail>(25)
|
||||
|
||||
sealed interface GalleryDetailScreenArgs : Parcelable
|
||||
@@ -139,6 +139,19 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n
|
||||
}
|
||||
}
|
||||
|
||||
val voteTag: VoteTag = { tag, vote ->
|
||||
runSuspendCatching {
|
||||
EhEngine.voteTag(apiUid, apiKey, gid, token, tag, vote)
|
||||
}.onSuccess { result ->
|
||||
val new = copy(tagGroups = result).apply { fillInfo() }
|
||||
detailCache[gid] = new
|
||||
galleryInfo = new
|
||||
showTip(R.string.tag_vote_successfully)
|
||||
}.onFailure { e ->
|
||||
showTip(e.displayString())
|
||||
}
|
||||
}
|
||||
|
||||
val signInFirst = stringResource(R.string.sign_in_first)
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -308,6 +321,7 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n
|
||||
contentPadding = it,
|
||||
getDetailError = getDetailError,
|
||||
onRetry = { getDetailError = "" },
|
||||
voteTag = voteTag,
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
)
|
||||
} else if (getDetailError.isNotBlank()) {
|
||||
@@ -322,18 +336,3 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context(Context, SnackbarHostState)
|
||||
suspend fun GalleryDetail.voteTag(tag: String, vote: Int) {
|
||||
runSuspendCatching {
|
||||
EhEngine.voteTag(apiUid, apiKey, gid, token, tag, vote)
|
||||
}.onSuccess { result ->
|
||||
if (result != null) {
|
||||
showSnackbar(result)
|
||||
} else {
|
||||
showSnackbar(getString(R.string.tag_vote_successfully))
|
||||
}
|
||||
}.onFailure {
|
||||
showSnackbar(getString(R.string.vote_failed))
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,10 @@ fun EhScreen(navigator: DestinationsNavigator) {
|
||||
title = stringResource(id = R.string.settings_eh_show_gallery_pages),
|
||||
summary = stringResource(id = R.string.settings_eh_show_gallery_pages_summary),
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_eh_show_vote_status),
|
||||
value = Settings::showVoteStatus,
|
||||
)
|
||||
val showComments = Settings::showComments.observed
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_eh_show_gallery_comments),
|
||||
|
||||
@@ -52,19 +52,21 @@ inline fun <reified T : Any> rememberInVM(
|
||||
}
|
||||
|
||||
@Composable
|
||||
inline fun <reified T : Any> rememberInVM(
|
||||
key: Any?,
|
||||
inline fun <T : Any, K : Any> rememberInVM(
|
||||
key: K,
|
||||
crossinline init: @DisallowComposableCalls ViewModel.() -> T,
|
||||
) = with(viewModel<StateMapViewModel>()) {
|
||||
val compositeKey = currentCompositeKeyHash
|
||||
remember(key) {
|
||||
val states = statesMap.getOrPut(compositeKey, ::ArrayDeque)
|
||||
states.removeLastOrNull() as T? ?: init()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(states.removeLastOrNull() as Pair<K, T>?)?.let { (k, v) -> v.takeIf { k == key } } ?: init()
|
||||
}.also { value ->
|
||||
val keyState by rememberUpdatedState(key)
|
||||
val valueState by rememberUpdatedState(value)
|
||||
DisposableEffect(compositeKey) {
|
||||
onDispose {
|
||||
statesMap[compositeKey]?.addFirst(valueState)
|
||||
statesMap[compositeKey]?.addFirst(keyState to valueState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +300,7 @@
|
||||
<string name="settings_eh_show_jpn_title_summary">需同时在 EHentai 网站设置中启用 Japanese Title</string>
|
||||
<string name="settings_eh_show_gallery_pages">显示画廊页数</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">在画廊列表中显示页数</string>
|
||||
<string name="settings_eh_show_vote_status">显示标签投票状态</string>
|
||||
<string name="settings_eh_show_gallery_comments">显示画廊评论</string>
|
||||
<string name="settings_eh_show_gallery_comments_summary">在画廊详情页中显示评论</string>
|
||||
<string name="settings_eh_show_gallery_comment_threshold">评论分数阈值</string>
|
||||
@@ -456,6 +457,7 @@
|
||||
<string name="toplist">排行</string>
|
||||
<string name="tag_vote_down">不,这不是</string>
|
||||
<string name="tag_vote_up">是的,这很正确</string>
|
||||
<string name="tag_vote_withdraw">撤回投票</string>
|
||||
<string name="tag_vote_successfully">投票成功</string>
|
||||
<string name="delete_search_history">从搜索记录中删除“%s”?</string>
|
||||
<string name="action_add_tag">添加标签</string>
|
||||
|
||||
@@ -316,6 +316,7 @@
|
||||
<string name="settings_eh_show_jpn_title_summary">Require enabling Japanese Title in Settings on EHentai website</string>
|
||||
<string name="settings_eh_show_gallery_pages">Show gallery pages</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">Display the number of pages in the gallery list</string>
|
||||
<string name="settings_eh_show_vote_status">Show tag vote status</string>
|
||||
<string name="settings_eh_show_gallery_comments">Show gallery comments</string>
|
||||
<string name="settings_eh_show_gallery_comments_summary">Show comments on the gallery details page</string>
|
||||
<string name="settings_eh_show_gallery_comment_threshold">Comment score threshold</string>
|
||||
@@ -473,6 +474,7 @@
|
||||
<string name="toplist">Toplist</string>
|
||||
<string name="tag_vote_down">Vote down</string>
|
||||
<string name="tag_vote_up">Vote up</string>
|
||||
<string name="tag_vote_withdraw">Withdraw vote</string>
|
||||
<string name="tag_vote_successfully">Vote successfully</string>
|
||||
<string name="delete_search_history">Delete \"%s\" from search history?</string>
|
||||
<string name="action_add_tag">Add tag</string>
|
||||
|
||||
Reference in new issue
Block a user