diff --git a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt index ee09be385..30e11975a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt @@ -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) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/EhRequestBuilder.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/EhRequestBuilder.kt index d296e903d..2a3fd0c9d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhRequestBuilder.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhRequestBuilder.kt @@ -50,7 +50,10 @@ suspend inline fun HttpStatement.executeAndParseAs() = executeSafely inline fun BufferedSource.parseAs(): T = json.decodeFromBufferedSource(this) inline fun 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) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt index 6c147a7ff..28cfabdca 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt @@ -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 + } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt new file mode 100644 index 000000000..5dd4921b1 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt @@ -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 diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt index 8591eebc6..1ac423e1f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt @@ -21,5 +21,5 @@ import kotlinx.parcelize.Parcelize @Parcelize data class GalleryTagGroup( val nameSpace: TagNamespace, - val tags: List, + val tags: List, ) : Parcelable diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt index a96ec17de..99a385b22 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt @@ -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 = Either.catch { + fun parseTagGroups(document: Document): List = Either.catch { val taglist = document.getElementById("taglist")!! taglist.child(0).child(0).children().mapNotNull(::parseSingleTagGroup) }.getOrElse { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryTokenApiParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryTokenApiParser.kt index b80784f7e..e35006ba8 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryTokenApiParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryTokenApiParser.kt @@ -36,7 +36,7 @@ object GalleryTokenApiParser { fun parse(body: String): String = Either.catch { body.parseAs().tokenList[0].token }.getOrElse { - throw EhException(body.parseAs().error) + throw EhException(body.parseAs().error) } @Serializable diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt index bb0790376..23a8ae30e 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt @@ -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 }.getOrNull() + fun parse(body: String): List = body.parseAs().let { (error, tagpane) -> + error?.let { error(error) } + checkNotNull(tagpane) + GalleryDetailParser.parseTagGroups(Jsoup.parse("
$tagpane
")) + } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/Error.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagResult.kt similarity index 55% rename from app/src/main/kotlin/com/hippo/ehviewer/client/parser/Error.kt rename to app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagResult.kt index 255057ac3..14e327d1d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/Error.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagResult.kt @@ -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?, +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt index 825d6fac1..fd20c4720 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt @@ -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) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt index 2c8e0e5ba..cb43d35b5 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt @@ -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) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryTag.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryTag.kt index 367593760..055af0894 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryTag.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryTag.kt @@ -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, 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) }, ), ) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt index 498c52165..32abfdc1c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt @@ -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.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.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) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt index 427bbcc7c..e7535a78a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt @@ -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(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)) - } -} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt index 3c57d307c..35bbbb2b3 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt @@ -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), diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/ViewModelState.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/ViewModelState.kt index a94d6b343..6d1069b22 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/ViewModelState.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/ViewModelState.kt @@ -52,19 +52,21 @@ inline fun rememberInVM( } @Composable -inline fun rememberInVM( - key: Any?, +inline fun rememberInVM( + key: K, crossinline init: @DisallowComposableCalls ViewModel.() -> T, ) = with(viewModel()) { val compositeKey = currentCompositeKeyHash remember(key) { val states = statesMap.getOrPut(compositeKey, ::ArrayDeque) - states.removeLastOrNull() as T? ?: init() + @Suppress("UNCHECKED_CAST") + (states.removeLastOrNull() as Pair?)?.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) } } } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cd07a821d..22daf181a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -300,6 +300,7 @@ 需同时在 EHentai 网站设置中启用 Japanese Title 显示画廊页数 在画廊列表中显示页数 + 显示标签投票状态 显示画廊评论 在画廊详情页中显示评论 评论分数阈值 @@ -456,6 +457,7 @@ 排行 不,这不是 是的,这很正确 + 撤回投票 投票成功 从搜索记录中删除“%s”? 添加标签 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f0a6adc7a..7b0c36174 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -316,6 +316,7 @@ Require enabling Japanese Title in Settings on EHentai website Show gallery pages Display the number of pages in the gallery list + Show tag vote status Show gallery comments Show comments on the gallery details page Comment score threshold @@ -473,6 +474,7 @@ Toplist Vote down Vote up + Withdraw vote Vote successfully Delete \"%s\" from search history? Add tag