Implement automatic update checks
This commit is contained in:
1 parent
a8c96fa7df
commit
d9522e4e89
9 files changed
+170
-31
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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<Int>) {
|
||||
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<String>) {
|
||||
val entryArray = stringArrayResource(id = entry)
|
||||
|
||||
@@ -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<GithubCommitComparison>()
|
||||
// TODO: Prettier format, Markdown?
|
||||
result.commits.joinToString("\n") { "${it.commit.message} (@${it.author.name})" }
|
||||
}.getOrDefault(workflowRun.title)
|
||||
return Release(shortSha, changelog, archiveUrl)
|
||||
|
||||
@@ -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<Int, String> = mapOf(),
|
||||
) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
fun edit(item: Map.Entry<Int, String>) {
|
||||
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,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,6 +510,13 @@
|
||||
<string name="list_tile_thumb_size">详情模式下缩略图大小</string>
|
||||
<string name="account_name">账户</string>
|
||||
<string name="preload_thumb_aggressively">激进地预载缩略图</string>
|
||||
<string name="auto_updates">自动检查更新</string>
|
||||
<string name="update_frequency_never">从不</string>
|
||||
<string name="update_frequency_daily">每天</string>
|
||||
<string name="update_frequency_3days">每三天</string>
|
||||
<string name="update_frequency_weekly">每周</string>
|
||||
<string name="update_frequency_biweekly">每两周</string>
|
||||
<string name="update_frequency_monthly">每月</string>
|
||||
<string name="new_version_available">有新版本!</string>
|
||||
<string name="already_latest_version">已是最新版本</string>
|
||||
<!-- Tachiyomi Reader Start-->
|
||||
|
||||
@@ -320,4 +320,22 @@
|
||||
<item>20</item>
|
||||
<item>25</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="update_frequency">
|
||||
<item>@string/update_frequency_never</item>
|
||||
<item>@string/update_frequency_daily</item>
|
||||
<item>@string/update_frequency_3days</item>
|
||||
<item>@string/update_frequency_weekly</item>
|
||||
<item>@string/update_frequency_biweekly</item>
|
||||
<item>@string/update_frequency_monthly</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="update_frequency_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>3</item>
|
||||
<item>7</item>
|
||||
<item>14</item>
|
||||
<item>30</item>
|
||||
</integer-array>
|
||||
</resources>
|
||||
@@ -573,6 +573,13 @@
|
||||
<string name="list_tile_thumb_size">Thumb size in thumb mode</string>
|
||||
<string name="account_name">Account</string>
|
||||
<string name="preload_thumb_aggressively">Preload thumbs aggressively</string>
|
||||
<string name="auto_updates">Automatically check for updates</string>
|
||||
<string name="update_frequency_never">Never</string>
|
||||
<string name="update_frequency_daily">Daily</string>
|
||||
<string name="update_frequency_3days">Every 3 days</string>
|
||||
<string name="update_frequency_weekly">Weekly</string>
|
||||
<string name="update_frequency_biweekly">Biweekly</string>
|
||||
<string name="update_frequency_monthly">Monthly</string>
|
||||
<string name="new_version_available">New version available!</string>
|
||||
<string name="already_latest_version">Already the latest version</string>
|
||||
<!-- Tachiyomi Reader Start-->
|
||||
|
||||
Reference in new issue
Block a user