From d9522e4e89ade2f602fd8b380901bf4f3f849c20 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Tue, 11 Jul 2023 02:20:06 +0800 Subject: [PATCH] Implement automatic update checks --- .../main/java/com/hippo/ehviewer/Settings.kt | 2 + .../ehviewer/ui/scene/GalleryListScene.kt | 14 ++++ .../hippo/ehviewer/ui/settings/AboutScreen.kt | 67 ++++++++++--------- .../ehviewer/ui/settings/PreferenceWidgets.kt | 16 +++++ .../com/hippo/ehviewer/updater/AppUpdater.kt | 9 ++- .../composeprefs3/ui/prefs/DropDownPref.kt | 61 +++++++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 7 ++ app/src/main/res/values/arrays.xml | 18 +++++ app/src/main/res/values/strings.xml | 7 ++ 9 files changed, 170 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.kt b/app/src/main/java/com/hippo/ehviewer/Settings.kt index 4f9b18ba8..2d1a9fd4b 100755 --- a/app/src/main/java/com/hippo/ehviewer/Settings.kt +++ b/app/src/main/java/com/hippo/ehviewer/Settings.kt @@ -74,12 +74,14 @@ object Settings : DataStorePreferences(null) { var requestNewsTimerHour by intPref("request_news_timer_hour", -1) var requestNewsTimerMinute by intPref("request_news_timer_minute", -1) var dataMapNextId by intPref("data_map_next_id", 0) + var updateIntervalDays by intPref("update_interval_days", 0) var recentDownloadLabel by stringOrNullPref("recent_download_label", null) var defaultDownloadLabel by stringOrNullPref("default_download_label", null) var displayName by stringOrNullPref("display_name", null) var avatar by stringOrNullPref("avatar", null) var language by stringPref("app_language", "system").observed { updateWhenLocaleChanges() } var lastDawnDay by longPref("last_dawn_day", 0) + var lastUpdateDay by longPref("last_update_day", 0) init { if ("CN" == Locale.getDefault().country) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt index d40894155..569ac7144 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt @@ -90,7 +90,9 @@ import com.hippo.ehviewer.ui.legacy.GalleryInfoContentHelper import com.hippo.ehviewer.ui.legacy.ViewTransition import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper import com.hippo.ehviewer.ui.setMD3Content +import com.hippo.ehviewer.ui.settings.showNewVersion import com.hippo.ehviewer.ui.tools.DialogState +import com.hippo.ehviewer.updater.AppUpdater import com.hippo.ehviewer.util.getParcelableCompat import com.hippo.ehviewer.yorozuya.AnimationUtils import com.hippo.ehviewer.yorozuya.SimpleAnimatorListener @@ -289,6 +291,17 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, OnClickFabList private val dialogState = DialogState() + // TODO: Move this out + private fun checkForUpdates() { + lifecycleScope.launchIO { + runSuspendCatching { + AppUpdater.checkForUpdate()?.let { + dialogState.showNewVersion(requireContext(), it) + } + } + } + } + override fun onCreateViewWithToolbar( inflater: LayoutInflater, container: ViewGroup?, @@ -296,6 +309,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, OnClickFabList ): View { _binding = SceneGalleryListBinding.inflate(inflater, container, false) binding.root.addView(ComposeView(inflater.context).apply { setMD3Content { dialogState.Handler() } }) + checkForUpdates() requireActivity().onBackPressedDispatcher.addCallback(mCallback) mHideActionFabSlop = ViewConfiguration.get(requireContext()).scaledTouchSlop mShowActionFab = true diff --git a/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt b/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt index 09266cbde..7f36f94dc 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/settings/AboutScreen.kt @@ -1,9 +1,12 @@ package com.hippo.ehviewer.ui.settings +import android.content.Context import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Icon @@ -32,9 +35,11 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.ui.LICENSE_SCREEN import com.hippo.ehviewer.ui.LocalNavController import com.hippo.ehviewer.ui.tools.DialogState +import com.hippo.ehviewer.ui.tools.observed import com.hippo.ehviewer.ui.tools.rememberDialogState import com.hippo.ehviewer.ui.tools.toAnnotatedString import com.hippo.ehviewer.updater.AppUpdater +import com.hippo.ehviewer.updater.Release import com.hippo.ehviewer.util.ExceptionUtils import com.hippo.ehviewer.util.installPackage import eu.kanade.tachiyomi.util.lang.withUIContext @@ -64,31 +69,6 @@ fun AboutScreen() { val dialogState = rememberDialogState() dialogState.Handler() fun launchSnackBar(content: String) = coroutineScope.launch { snackbarHostState.showSnackbar(content) } - suspend fun DialogState.showNewVersion( - name: String, - changelog: String, - downloadTo: suspend (File) -> Unit, - ) { - val download = show( - confirmText = R.string.download, - dismissText = android.R.string.cancel, - title = R.string.new_version_available, - ) { - Column { - Text( - text = name, - style = MaterialTheme.typography.headlineSmall, - ) - Spacer(modifier = Modifier.height(16.dp)) - Text(text = changelog) - } - } - if (download) { - val file = File(AppConfig.tempDir, "update.apk").apply { delete() } - downloadTo(file) - withUIContext { context.installPackage(file) } - } - } Scaffold( topBar = { TopAppBar( @@ -103,7 +83,7 @@ fun AboutScreen() { }, snackbarHost = { SnackbarHost(snackbarHostState) }, ) { paddingValues -> - Column(modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection)) { + Column(modifier = Modifier.padding(paddingValues).nestedScroll(scrollBehavior.nestedScrollConnection).verticalScroll(rememberScrollState())) { Preference( title = stringResource(id = R.string.settings_about_declaration), summary = stringResource(id = R.string.settings_about_declaration_summary), @@ -131,12 +111,16 @@ fun AboutScreen() { title = stringResource(id = R.string.use_ci_update_channel), value = Settings::useCIUpdateChannel, ) + SimpleMenuPreferenceInt2( + title = stringResource(id = R.string.auto_updates), + entry = R.array.update_frequency, + entryValueRes = R.array.update_frequency_values, + value = Settings::updateIntervalDays.observed, + ) WorkPreference(title = stringResource(id = R.string.settings_about_check_for_updates)) { runSuspendCatching { - AppUpdater.checkForUpdate(true)?.run { - dialogState.showNewVersion(version, changelog) { file -> - AppUpdater.downloadUpdate(downloadLink, file) - } + AppUpdater.checkForUpdate(true)?.let { + dialogState.showNewVersion(context, it) } ?: launchSnackBar(context.getString(R.string.already_latest_version)) }.onFailure { launchSnackBar(ExceptionUtils.getReadableString(it)) @@ -145,3 +129,26 @@ fun AboutScreen() { } } } + +suspend fun DialogState.showNewVersion(context: Context, release: Release) { + val download = show( + confirmText = R.string.download, + dismissText = android.R.string.cancel, + title = R.string.new_version_available, + ) { + Column { + Text( + text = release.version, + style = MaterialTheme.typography.headlineSmall, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text(text = release.changelog) + } + } + // TODO: Download in the background and show progress in notification + if (download) { + val file = File(AppConfig.tempDir, "update.apk").apply { delete() } + AppUpdater.downloadUpdate(release.downloadLink, file) + withUIContext { context.installPackage(file) } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt b/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt index a1d45d4ec..c77a4e85b 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.integerArrayResource import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -40,6 +41,7 @@ import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder import com.hippo.ehviewer.ui.openBrowser import com.hippo.ehviewer.ui.settings.PreferenceTokens.PreferenceTextPadding import com.jamal.composeprefs3.ui.prefs.DropDownPref +import com.jamal.composeprefs3.ui.prefs.DropDownPrefInt import com.jamal.composeprefs3.ui.prefs.SliderPref import com.jamal.composeprefs3.ui.prefs.SpannedTextPref import com.jamal.composeprefs3.ui.prefs.SwitchPref @@ -121,6 +123,20 @@ fun SimpleMenuPreferenceInt(title: String, summary: String? = null, @ArrayRes en DropDownPref(title = title, summary = summary, defaultValue = v.toString(), onValueChange = ::set, useSelectedAsSummary = summary.isNullOrBlank(), entries = map) } +@Composable +fun SimpleMenuPreferenceInt2(title: String, summary: String? = null, @ArrayRes entry: Int, @ArrayRes entryValueRes: Int, value: MutableState) { + val entryArray = stringArrayResource(id = entry) + val valuesArray = integerArrayResource(id = entryValueRes) + val map = remember { + val iter = entryArray.iterator() + valuesArray.associateWith { iter.next() } + } + var v by value + fun set(new: Int) { v = new } + check(entryArray.size == valuesArray.size) + DropDownPrefInt(title = title, summary = summary, defaultValue = v, onValueChange = ::set, useSelectedAsSummary = summary.isNullOrBlank(), entries = map) +} + @Composable fun SimpleMenuPreference(title: String, @ArrayRes entry: Int, @ArrayRes entryValueRes: Int, value: KMutableProperty0) { val entryArray = stringArrayResource(id = entry) diff --git a/app/src/main/java/com/hippo/ehviewer/updater/AppUpdater.kt b/app/src/main/java/com/hippo/ehviewer/updater/AppUpdater.kt index 4cecc1d5c..966e8a0ef 100644 --- a/app/src/main/java/com/hippo/ehviewer/updater/AppUpdater.kt +++ b/app/src/main/java/com/hippo/ehviewer/updater/AppUpdater.kt @@ -15,6 +15,8 @@ import tachiyomi.data.release.GithubCommitComparison import tachiyomi.data.release.GithubRelease import tachiyomi.data.release.GithubWorkflowRuns import java.io.File +import java.time.Instant +import java.time.temporal.ChronoUnit import java.util.zip.ZipInputStream private const val API_URL = "https://api.github.com/repos/${BuildConfig.REPO_NAME}" @@ -22,7 +24,11 @@ private const val LATEST_RELEASE_URL = "$API_URL/releases/latest" object AppUpdater { suspend fun checkForUpdate(forceCheck: Boolean = false): Release? { - if (forceCheck) { + val now = Instant.now() + val lastChecked = Instant.ofEpochSecond(Settings.lastUpdateDay) + val interval = Settings.updateIntervalDays + if (forceCheck || interval != 0 && now.isAfter(lastChecked.plus(interval.toLong(), ChronoUnit.DAYS))) { + Settings.lastUpdateDay = now.epochSecond if (Settings.useCIUpdateChannel) { val curSha = BuildConfig.COMMIT_SHA val branch = ghRequest(API_URL).execute { @@ -37,6 +43,7 @@ object AppUpdater { val changelog = runSuspendCatching { val commitComparisonUrl = "$API_URL/compare/$curSha...$shortSha" val result = ghRequest(commitComparisonUrl).executeAndParseAs() + // TODO: Prettier format, Markdown? result.commits.joinToString("\n") { "${it.commit.message} (@${it.author.name})" } }.getOrDefault(workflowRun.title) return Release(shortSha, changelog, archiveUrl) diff --git a/app/src/main/java/com/jamal/composeprefs3/ui/prefs/DropDownPref.kt b/app/src/main/java/com/jamal/composeprefs3/ui/prefs/DropDownPref.kt index c9da02c6c..e2a8f3811 100644 --- a/app/src/main/java/com/jamal/composeprefs3/ui/prefs/DropDownPref.kt +++ b/app/src/main/java/com/jamal/composeprefs3/ui/prefs/DropDownPref.kt @@ -77,3 +77,64 @@ fun DropDownPref( } } } + +@Composable +fun DropDownPrefInt( + title: String, + modifier: Modifier = Modifier, + summary: String? = null, + defaultValue: Int? = null, + onValueChange: ((Int) -> Unit)? = null, + useSelectedAsSummary: Boolean = false, + dropdownBackgroundColor: Color? = null, + textColor: Color = MaterialTheme.colorScheme.onBackground, + enabled: Boolean = true, + entries: Map = mapOf(), +) { + var expanded by rememberSaveable { mutableStateOf(false) } + fun edit(item: Map.Entry) { + expanded = false + onValueChange?.invoke(item.key) + } + + Column { + TextPref( + title = title, + modifier = modifier, + summary = when { + useSelectedAsSummary && defaultValue != null -> entries[defaultValue] + useSelectedAsSummary && defaultValue == null -> "Not Set" + else -> summary + }, + textColor = textColor, + enabled = enabled, + onClick = { + expanded = true + }, + ) + + Box( + modifier = Modifier.padding(start = 16.dp), + ) { + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = if (dropdownBackgroundColor != null) Modifier.background(dropdownBackgroundColor) else Modifier, + ) { + entries.forEach { item -> + DropdownMenuItem( + onClick = { + edit(item) + }, + text = { + Text( + text = item.value, + style = MaterialTheme.typography.bodyLarge, + ) + }, + ) + } + } + } + } +} diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ed5952ec1..e025d5536 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -510,6 +510,13 @@ 详情模式下缩略图大小 账户 激进地预载缩略图 + 自动检查更新 + 从不 + 每天 + 每三天 + 每周 + 每两周 + 每月 有新版本! 已是最新版本 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index cddd43fe1..acacee360 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -320,4 +320,22 @@ 20 25 + + + @string/update_frequency_never + @string/update_frequency_daily + @string/update_frequency_3days + @string/update_frequency_weekly + @string/update_frequency_biweekly + @string/update_frequency_monthly + + + + 0 + 1 + 3 + 7 + 14 + 30 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 841286b37..e56a6e3fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -573,6 +573,13 @@ Thumb size in thumb mode Account Preload thumbs aggressively + Automatically check for updates + Never + Daily + Every 3 days + Weekly + Biweekly + Monthly New version available! Already the latest version