From 2c0d621b1637bd14e99f467edca652a8d5e5d775 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <99822064+revonateB0T@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:36:02 +0800 Subject: [PATCH] Add avatar (#1720) * Load Avatar * Click Avatar to show image limit * Shared Limit among collection * Cleanup and configure heartbeat * Always refresh avatars * Refine icons * Avoid heartbeat * Fix mistake * No need to SharingStarted.WhileSubscribed * Address code review * Simply map error msg * Convert limitFlow to stateFlow * Fixes * Refine retry logic * Don't retry automatically * Don't cache error result * Animate quota changes * Logout from guest mode via avatar * No need to navigate explicitly as needSignIn is observed * Make PostLogin not suspend * More UX friendly * Fix progressIndicator not disappear * Use Either.catch.mapLeft * Refine * Add overload for awaitConfirmationOrCancel * Log error with snackbar * Refine * Fix to use collectLatest * Refine * Refine * Address cr * Refine * Animated loading * Refine * Fix * Fix * Refine image limits dialog --------- Co-authored-by: Dude so hot Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- .../kotlin/com/hippo/ehviewer/Settings.kt | 3 +- .../com/hippo/ehviewer/client/EhUtils.kt | 2 +- .../ehviewer/client/parser/HomeParser.kt | 3 +- .../com/hippo/ehviewer/ui/MainActivity.kt | 3 +- .../com/hippo/ehviewer/ui/login/PostLogin.kt | 31 ++- .../hippo/ehviewer/ui/login/SignInScreen.kt | 14 +- .../ehviewer/ui/login/WebViewSignInScreen.kt | 31 +-- .../com/hippo/ehviewer/ui/main/AvatarIcon.kt | 201 ++++++++++++++++++ .../ehviewer/ui/screen/FavoritesScreen.kt | 2 + .../ehviewer/ui/screen/GalleryListScreen.kt | 2 + .../hippo/ehviewer/ui/settings/EhScreen.kt | 147 ++----------- .../com/hippo/ehviewer/ui/tools/Dialog.kt | 8 +- app/src/main/res/values-zh-rCN/strings.xml | 3 +- app/src/main/res/values-zh-rTW/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 15 files changed, 270 insertions(+), 186 deletions(-) create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/main/AvatarIcon.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt index 575d1629e..420fc0d09 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt @@ -116,6 +116,7 @@ object Settings : DataStorePreferences(null) { val downloadSortMode = intPref("download_sort_mode", SortMode.Default.flag) val downloadFilterMode = intPref("download_filter_mode", DownloadsFilterMode.Default.flag) val hasSignedIn = boolPref("has_signed_in", EhCookieStore.hasSignedIn()) + val needSignIn = boolPref("need_sign_in", true) val meteredNetworkWarning = boolPref("cellular_network_warning", false) val blackDarkTheme = boolPref("black_dark_theme", false) val gridView = boolPref("grid_view", false) @@ -124,6 +125,7 @@ object Settings : DataStorePreferences(null) { val security = boolPref("require_unlock", false) val animateItems = boolPref("animate_items", true) val displayName = stringOrNullPref("display_name", null) + val avatar = stringOrNullPref("avatar", null) val recentDownloadLabel = stringOrNullPref("recent_download_label", null) var downloadScheme by stringOrNullPref("image_scheme", null) @@ -157,7 +159,6 @@ object Settings : DataStorePreferences(null) { var saveParseErrorBody by boolPref("save_parse_error_body", true) var saveCrashLog by boolPref("save_crash_log", true) var removeImageFiles by boolPref("include_pic", true) - var needSignIn by boolPref("need_sign_in", true) var harmonizeCategoryColor by boolPref("harmonize_category_color", true) var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false) var downloadOriginImage by boolPref("download_origin_image", false) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/EhUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/EhUtils.kt index f36087bf9..856767b96 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhUtils.kt @@ -153,7 +153,7 @@ object EhUtils { Settings.displayName.value = null Settings.hasSignedIn.value = false Settings.gallerySite = EhUrl.SITE_E - Settings.needSignIn = true + Settings.needSignIn.value = true } fun getSuitableTitle(gi: GalleryInfo): String = if (Settings.showJpnTitle) { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt index 048cc7785..202f17409 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt @@ -4,6 +4,7 @@ import android.os.Parcelable import com.hippo.ehviewer.client.exception.InsufficientFundsException import com.hippo.ehviewer.client.exception.ParseException import java.nio.ByteBuffer +import kotlin.random.Random import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable @@ -35,7 +36,7 @@ object HomeParser { } @Parcelize - data class Result(val limits: Limits, val funds: Funds) : Parcelable + data class Result(val limits: Limits, val funds: Funds, private val id: Int = Random.nextInt()) : Parcelable } @Parcelize 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 e1eb6f23d..f9dfd41e4 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt @@ -376,6 +376,7 @@ class MainActivity : EhActivity() { val drawerEnabled = drawerHandle.isNotEmpty() val density = LocalDensity.current val adaptiveInfo = currentWindowAdaptiveInfo() + val needSignIn by Settings.needSignIn.collectAsState() CompositionLocalProvider( LocalNavDrawerState provides navDrawerState, LocalSideSheetState provides sideSheetState, @@ -473,7 +474,7 @@ class MainActivity : EhActivity() { // SharedTransitionLayout { CompositionLocalProvider(LocalSharedTransitionScope provides NoopSharedTransitionScope) { val start = when { - Settings.needSignIn -> SignInScreenDestination + needSignIn -> SignInScreenDestination hasNetwork -> StartDestination else -> DownloadsScreenDestination } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/login/PostLogin.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/login/PostLogin.kt index 38f195a08..dfc8e51ba 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/login/PostLogin.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/login/PostLogin.kt @@ -5,19 +5,26 @@ import com.hippo.ehviewer.client.EhCookieStore import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhUrl import eu.kanade.tachiyomi.util.system.logcat -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async import kotlinx.coroutines.launch +import moe.tarsin.coroutines.runSuspendCatching -suspend fun postLogin() = coroutineScope { - launch { - runCatching { - EhEngine.getProfile().run { - Settings.displayName.value = displayName - } - }.onFailure { - logcat(it) - } +suspend fun CoroutineScope.refreshAccountInfo() = runSuspendCatching { + with(EhEngine.getProfile()) { + Settings.displayName.value = displayName + Settings.avatar.value = avatar } +}.onFailure { + logcat(it) +} + +@OptIn(DelicateCoroutinesApi::class) +fun postLogin() = GlobalScope.async(Dispatchers.IO) { + launch { refreshAccountInfo() } runCatching { // For the `star` cookie EhEngine.getNews(false) @@ -34,12 +41,14 @@ suspend fun postLogin() = coroutineScope { // Sad panda check Settings.gallerySite = EhUrl.SITE_EX + // Explicitly use ex url since https://github.com/Ehviewer-Overhauled/Ehviewer/issues/1239#issuecomment-1632584525 EhEngine.getUConfig(EhUrl.URL_UCONFIG_EX) EhCookieStore.flush() }.onFailure { Settings.gallerySite = EhUrl.SITE_E } + Settings.hasSignedIn.value = true - Settings.needSignIn = false + Settings.needSignIn.value = false } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/login/SignInScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/login/SignInScreen.kt index d49ee8b02..2778a294b 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/login/SignInScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/login/SignInScreen.kt @@ -60,10 +60,8 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.EhUtils -import com.hippo.ehviewer.ui.StartDestination import com.hippo.ehviewer.ui.destinations.WebViewSignInScreenDestination import com.hippo.ehviewer.ui.openBrowser -import com.hippo.ehviewer.ui.screen.popNavigate import com.hippo.ehviewer.ui.tools.LocalDialogState import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass import com.hippo.ehviewer.ui.tools.autofill @@ -72,7 +70,6 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.withNonCancellableContext import eu.kanade.tachiyomi.util.lang.withUIContext import kotlinx.coroutines.Job @@ -116,6 +113,7 @@ fun SignInScreen(navigator: DestinationsNavigator) { }.onFailure { withUIContext { focusManager.clearFocus() + isProgressIndicatorVisible = false dialogState.awaitConfirmationOrCancel( confirmText = R.string.get_it, title = R.string.sign_in_failed, @@ -129,11 +127,9 @@ fun SignInScreen(navigator: DestinationsNavigator) { ) }, ) - isProgressIndicatorVisible = false } }.onSuccess { - withNonCancellableContext { postLogin() } - withUIContext { navigator.popNavigate(StartDestination) } + postLogin() } } } @@ -236,9 +232,8 @@ fun SignInScreen(navigator: DestinationsNavigator) { } TextButton( onClick = { - Settings.needSignIn = false Settings.gallerySite = EhUrl.SITE_E - navigator.popNavigate(StartDestination) + Settings.needSignIn.value = false }, modifier = Modifier.weight(1f), ) { @@ -319,9 +314,8 @@ fun SignInScreen(navigator: DestinationsNavigator) { } TextButton( onClick = { - Settings.needSignIn = false Settings.gallerySite = EhUrl.SITE_E - navigator.popNavigate(StartDestination) + Settings.needSignIn.value = false }, modifier = Modifier.padding(horizontal = 4.dp).width(128.dp), ) { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/login/WebViewSignInScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/login/WebViewSignInScreen.kt index 9992ab171..ee43da05b 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/login/WebViewSignInScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/login/WebViewSignInScreen.kt @@ -1,11 +1,11 @@ package com.hippo.ehviewer.ui.login import android.webkit.WebView +import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import com.google.accompanist.web.AccompanistWebViewClient import com.google.accompanist.web.WebView @@ -13,35 +13,26 @@ import com.google.accompanist.web.rememberWebViewState import com.hippo.ehviewer.client.EhCookieStore import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.EhUtils -import com.hippo.ehviewer.ui.StartDestination -import com.hippo.ehviewer.ui.screen.popNavigate +import com.hippo.ehviewer.ui.composing +import com.hippo.ehviewer.util.bgWork import com.hippo.ehviewer.util.setDefaultSettings import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.withNonCancellableContext -import eu.kanade.tachiyomi.util.lang.withUIContext +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.launch @Destination @Composable -fun WebViewSignInScreen(navigator: DestinationsNavigator) { - val coroutineScope = rememberCoroutineScope() +fun AnimatedVisibilityScope.WebViewSignInScreen(navigator: DestinationsNavigator) = composing(navigator) { val state = rememberWebViewState(url = EhUrl.URL_SIGN_IN) val client = remember { object : AccompanistWebViewClient() { - private var present = false override fun onPageFinished(view: WebView, url: String?) { - if (present) { - view.destroy() - return - } if (EhCookieStore.hasSignedIn()) { - present = true - coroutineScope.launchIO { - withNonCancellableContext { postLogin() } - withUIContext { navigator.popNavigate(StartDestination) } - } + postLogin() + view.destroy() + launch { bgWork { awaitCancellation() } } } } } @@ -52,9 +43,7 @@ fun WebViewSignInScreen(navigator: DestinationsNavigator) { WebView( state = state, modifier = Modifier.fillMaxSize(), - onCreated = { - it.setDefaultSettings() - }, + onCreated = { it.setDefaultSettings() }, client = client, ) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AvatarIcon.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AvatarIcon.kt new file mode 100644 index 000000000..27db745a1 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AvatarIcon.kt @@ -0,0 +1,201 @@ +package com.hippo.ehviewer.ui.main + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.NoAccounts +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.RestartAlt +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import arrow.core.Either +import arrow.core.Either.Companion.catch +import arrow.core.Option +import arrow.core.none +import arrow.core.some +import coil3.compose.AsyncImage +import com.hippo.ehviewer.R +import com.hippo.ehviewer.Settings +import com.hippo.ehviewer.client.EhEngine +import com.hippo.ehviewer.client.EhUtils +import com.hippo.ehviewer.client.parser.HomeParser +import com.hippo.ehviewer.collectAsState +import com.hippo.ehviewer.ui.tools.DialogState +import com.hippo.ehviewer.util.displayString +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import eu.kanade.tachiyomi.util.lang.launchIO +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import moe.tarsin.coroutines.runSwallowingWithUI + +private val limitScope = CoroutineScope(Dispatchers.IO) +private val refreshEvent = MutableSharedFlow() +private val invalidateEvent = MutableSharedFlow() + +typealias Result = Option> + +private val limitFlow: StateFlow = refreshEvent.conflate() + .onEach { + // If cached value is error, drop it when refresh + val isError = limitFlow.value.isSome { it.isLeft() } + if (isError) invalidateEvent.emit(Unit) + } + .map { catch { EhEngine.getImageLimits() }.mapLeft { e -> e.displayString() }.some() } + .let { src -> merge(src, invalidateEvent.map { none() }) } + .stateIn(limitScope, SharingStarted.Eagerly, none()) + +context(CoroutineScope, DialogState, SnackbarHostState, DestinationsNavigator) +@Composable +fun AvatarIcon() { + val hasSignedIn by Settings.hasSignedIn.collectAsState() + if (hasSignedIn) { + val placeholder = stringResource(id = R.string.please_wait) + val result by limitFlow.collectAsState() + IconButton( + onClick = { + launch { + refreshEvent.emit(Unit) + awaitConfirmationOrCancel( + title = R.string.image_limits, + showConfirmButton = false, + showCancelButton = false, + ) { + val animatedAlpha by animateFloatAsState(if (remember { result } == result) 0.5f else 1f) + result.onNone { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircularProgressIndicator() + Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin))) + Text(text = placeholder) + } + } + result.onSome { current -> + Box(modifier = Modifier.graphicsLayer { alpha = animatedAlpha }) { + when (current) { + is Either.Left -> Text(text = current.value) + is Either.Right -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + val (limits, funds) = current.value + if (limits.maximum > 0) { + val value by animateFloatAsState(limits.current.toFloat() / limits.maximum) + LinearProgressIndicator( + progress = { value }, + modifier = Modifier.height(12.dp).fillMaxWidth(), + ) + } + Text( + text = when (limits.maximum) { + -1 -> stringResource(id = R.string.image_limits_restricted) + 0 -> stringResource(id = R.string.image_limits_normal) + else -> stringResource(id = R.string.image_limits_summary, limits.current, limits.maximum) + }, + ) + Text(text = stringResource(id = R.string.current_funds)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceAround, + ) { + FundsItem( + type = "GP", + amount = funds.gp, + ) + FundsItem( + type = "C", + amount = funds.credit, + ) + } + if (limits.resetCost > 0) { + Button( + onClick = { + launchIO { + runSwallowingWithUI { + invalidateEvent.emit(Unit) + EhEngine.resetImageLimits() + refreshEvent.emit(Unit) + } + } + }, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) { + Icon( + imageVector = Icons.Default.RestartAlt, + contentDescription = stringResource(id = R.string.reset), + ) + Text(text = stringResource(id = R.string.reset_cost, limits.resetCost)) + } + } + } + } + } + } + } + } + }, + ) { + val avatar by Settings.avatar.collectAsState() + AnimatedContent(targetState = avatar == null) { noAvatar -> + if (noAvatar) { + Icon(imageVector = Icons.Default.Person, contentDescription = null) + } else { + AsyncImage( + model = avatar, + contentDescription = null, + modifier = Modifier.clip(CircleShape), + contentScale = ContentScale.Crop, + ) + } + } + } + } else { + IconButton( + onClick = { + launch { + awaitConfirmationOrCancel( + confirmText = R.string.sign_in, + showCancelButton = false, + text = { Text(text = stringResource(id = R.string.settings_eh_identity_cookies_guest)) }, + ) + EhUtils.signOut() + } + }, + ) { + Icon(imageVector = Icons.Default.NoAccounts, contentDescription = null) + } + } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt index 1d67c6527..416da2a0e 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt @@ -68,6 +68,7 @@ import com.hippo.ehviewer.ui.DrawerHandle import com.hippo.ehviewer.ui.LocalSideSheetState import com.hippo.ehviewer.ui.awaitSelectDate import com.hippo.ehviewer.ui.composing +import com.hippo.ehviewer.ui.main.AvatarIcon import com.hippo.ehviewer.ui.main.FAB_ANIMATE_TIME import com.hippo.ehviewer.ui.main.FabLayout import com.hippo.ehviewer.ui.main.GalleryInfoGridItem @@ -246,6 +247,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = IconButton(onClick = { launch { sheetState.open() } }) { Icon(imageVector = Icons.Outlined.FolderSpecial, contentDescription = null) } + AvatarIcon() }, ) { contentPadding -> val listMode by Settings.listMode.collectAsState() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt index 0b6ec0638..36b50f972 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt @@ -99,6 +99,7 @@ import com.hippo.ehviewer.ui.composing import com.hippo.ehviewer.ui.destinations.ProgressScreenDestination import com.hippo.ehviewer.ui.doGalleryInfoAction import com.hippo.ehviewer.ui.main.AdvancedSearchOption +import com.hippo.ehviewer.ui.main.AvatarIcon import com.hippo.ehviewer.ui.main.FAB_ANIMATE_TIME import com.hippo.ehviewer.ui.main.FabLayout import com.hippo.ehviewer.ui.main.GalleryInfoGridItem @@ -522,6 +523,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De IconButton(onClick = { launch { sheetState.open() } }) { Icon(imageVector = Icons.Outlined.Bookmarks, contentDescription = stringResource(id = R.string.quick_search)) } + AvatarIcon() }, filter = { SearchFilter( 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 ed4fbb24e..28e2bfe76 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 @@ -1,12 +1,8 @@ package com.hippo.ehviewer.ui.settings import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState @@ -14,10 +10,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ContentCopy -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost @@ -26,15 +20,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -43,38 +32,28 @@ import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.fromHtml -import androidx.compose.ui.unit.dp import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.asMutableState import com.hippo.ehviewer.client.EhCookieStore -import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.client.EhUtils -import com.hippo.ehviewer.client.parser.HomeParser import com.hippo.ehviewer.collectAsState import com.hippo.ehviewer.ui.destinations.FilterScreenDestination import com.hippo.ehviewer.ui.destinations.MyTagsScreenDestination -import com.hippo.ehviewer.ui.destinations.SignInScreenDestination import com.hippo.ehviewer.ui.destinations.UConfigScreenDestination -import com.hippo.ehviewer.ui.main.FundsItem -import com.hippo.ehviewer.ui.screen.popNavigate import com.hippo.ehviewer.ui.tools.LocalDialogState import com.hippo.ehviewer.ui.tools.observed import com.hippo.ehviewer.ui.tools.rememberedAccessor import com.hippo.ehviewer.util.copyTextToClipboard -import com.hippo.ehviewer.util.displayString import com.hippo.ehviewer.util.isAtLeastT import com.jamal.composeprefs3.ui.prefs.SwitchPref import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import eu.kanade.tachiyomi.util.lang.withIOContext -import eu.kanade.tachiyomi.util.lang.withUIContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.datetime.LocalTime -import moe.tarsin.coroutines.runSuspendCatching @Destination @Composable @@ -100,7 +79,6 @@ fun EhScreen(navigator: DestinationsNavigator) { }, snackbarHost = { SnackbarHost(snackbarHostState) }, ) { paddingValues -> - val guestMode = stringResource(id = R.string.settings_eh_identity_cookies_guest) val copiedToClipboard = stringResource(id = R.string.copied_to_clipboard) Column( modifier = Modifier @@ -108,21 +86,21 @@ fun EhScreen(navigator: DestinationsNavigator) { .verticalScroll(rememberScrollState()) .padding(paddingValues), ) { - val displayName by Settings.displayName.collectAsState() - Preference( - title = stringResource(id = R.string.account_name), - summary = displayName ?: guestMode, - ) { - coroutineScope.launch { - val cookies = EhCookieStore.getIdentityCookies() - dialogState.awaitConfirmationOrCancel( - confirmText = R.string.settings_eh_sign_out, - dismissText = R.string.settings_eh_clear_igneous, - showCancelButton = cookies.last().second != null, - onCancelButtonClick = { EhCookieStore.clearIgneous() }, - secure = hasSignedIn, - ) { - if (hasSignedIn) { + if (hasSignedIn) { + val displayName by Settings.displayName.collectAsState() + Preference( + title = stringResource(id = R.string.account_name), + summary = displayName, + ) { + coroutineScope.launch { + val cookies = EhCookieStore.getIdentityCookies() + dialogState.awaitConfirmationOrCancel( + confirmText = R.string.settings_eh_sign_out, + dismissText = R.string.settings_eh_clear_igneous, + showCancelButton = cookies.last().second != null, + onCancelButtonClick = { EhCookieStore.clearIgneous() }, + secure = true, + ) { Column { val warning = stringResource(id = R.string.settings_eh_identity_cookies_signed) val str = cookies.joinToString("\n") { (k, v) -> "$k: $v" } @@ -143,101 +121,8 @@ fun EhScreen(navigator: DestinationsNavigator) { }, ) } - } else { - Text(text = guestMode) - } - } - EhUtils.signOut() - withUIContext { - navigator.popNavigate(SignInScreenDestination) - } - } - } - if (hasSignedIn) { - val placeholder = stringResource(id = R.string.please_wait) - val resetImageLimitSucceed = stringResource(id = R.string.reset_limits_succeed) - var result by rememberSaveable { mutableStateOf(null) } - var error by rememberSaveable { mutableStateOf(null) } - val summary by rememberUpdatedState( - result?.run { - when (limits.maximum) { - -1 -> stringResource(id = R.string.image_limits_restricted) - 0 -> stringResource(id = R.string.image_limits_normal) - else -> stringResource(id = R.string.image_limits_summary, limits.current, limits.maximum) - } - } ?: placeholder, - ) - suspend fun getImageLimits() { - result = EhEngine.getImageLimits() - error = null - } - if (result == null && error == null) { - LaunchedEffect(Unit) { - runSuspendCatching { - withIOContext { getImageLimits() } - }.onFailure { - error = it.displayString() - } - } - } - Preference( - title = stringResource(id = R.string.image_limits), - summary = summary, - ) { - coroutineScope.launch { - dialogState.awaitConfirmationOrCancel( - confirmText = R.string.reset, - title = R.string.image_limits, - confirmButtonEnabled = result?.run { limits.resetCost != 0 } ?: false, - ) { - error?.let { - Text(text = it) - } ?: result?.let { (limits, funds) -> - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - if (limits.maximum > 0) { - LinearProgressIndicator( - progress = { limits.current.toFloat() / limits.maximum }, - modifier = Modifier.height(12.dp).fillMaxWidth(), - ) - } - Text(text = summary) - if (limits.resetCost > 0) { - Text(text = stringResource(id = R.string.reset_cost, limits.resetCost)) - } - Text(text = stringResource(id = R.string.current_funds)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceAround, - ) { - FundsItem( - type = "GP", - amount = funds.gp, - ) - FundsItem( - type = "C", - amount = funds.credit, - ) - } - } - } ?: run { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - CircularProgressIndicator() - Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin))) - Text(text = placeholder) - } - } - } - runSuspendCatching { - EhEngine.resetImageLimits() - getImageLimits() - }.onSuccess { - launchSnackBar(resetImageLimitSucceed) - }.onFailure { - error = it.displayString() } + EhUtils.signOut() } } SimpleMenuPreferenceInt( diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt index e05cce628..d68548e9d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt @@ -276,7 +276,7 @@ class DialogState { @StringRes confirmText: Int = android.R.string.ok, @StringRes dismissText: Int = android.R.string.cancel, @StringRes title: Int? = null, - confirmButtonEnabled: Boolean = true, + showConfirmButton: Boolean = true, showCancelButton: Boolean = true, onCancelButtonClick: () -> Unit = {}, secure: Boolean = false, @@ -285,8 +285,10 @@ class DialogState { AlertDialog( onDismissRequest = { cont.cancel() }, confirmButton = { - TextButton(onClick = { cont.resume(Unit) }, enabled = confirmButtonEnabled) { - Text(text = stringResource(id = confirmText)) + if (showConfirmButton) { + TextButton(onClick = { cont.resume(Unit) }) { + Text(text = stringResource(id = confirmText)) + } } }, dismissButton = showCancelButton.ifTrueThen { diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index f44d60887..e1c96ca5b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -425,8 +425,7 @@ 已用:%1$d / %2$d 无限制 图片分辨率限制为 1280x - 重置花费:%d GP - 成功重置图片配额 + 花费 %d GP 重置 重置 隐私 不允许屏幕抓取 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f19acddec..1bbe54f11 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -419,8 +419,7 @@ 餘額不足 圖片配額 已用:%1$d / %2$d - 重置花費:%d GP - 成功重置圖片配額 + 花費 %d GP 重置 重置 隱私 不允許螢幕擷取 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ca2bb8656..114282d40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -446,8 +446,7 @@ Used: %1$d / %2$d No restrictions Image resolution restricted to 1280x - Reset Cost: %d GP - Image limit was successfully reset + Spend %d GP to reset Reset Privacy Prevent screenshots