diff --git a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt index 79b42a1e9..1ffcbf6d5 100644 --- a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt @@ -45,4 +45,9 @@ class BasePreferences( Preference.appStateKey("donation_campaign_shown"), false, ) + + val skippedAppUpdate: Preference = preferenceStore.getString( + Preference.appStateKey("skipped_app_update"), + "", + ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt index a1e6afcdd..f55b6999a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt @@ -5,12 +5,17 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.manga.components.MarkdownRender @@ -18,6 +23,7 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.tachiyomi.ui.more.NewUpdateScreenModel import mihon.icons.materialsymbols.MaterialSymbols import mihon.icons.materialsymbols.automirroredrounded.OpenInNew +import mihon.icons.materialsymbols.rounded.UpdateDisabled import mihon.icons.materialsymbols.rounded.Verified import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor import tachiyomi.i18n.MR @@ -34,7 +40,33 @@ fun NewUpdateScreen( onOpenInBrowser: () -> Unit, onAcceptUpdate: () -> Unit, onRejectUpdate: () -> Unit, + onSkipUpdate: () -> Unit, ) { + var showSkipDialog by remember { mutableStateOf(false) } + if (showSkipDialog) { + val dismiss = { showSkipDialog = false } + AlertDialog( + onDismissRequest = dismiss, + title = { Text(text = stringResource(MR.strings.update_check_skip_title, versionName)) }, + text = { Text(text = stringResource(MR.strings.update_check_skip_message)) }, + dismissButton = { + TextButton(onClick = dismiss) { + Text(text = stringResource(MR.strings.action_cancel)) + } + }, + confirmButton = { + TextButton( + onClick = { + dismiss() + onSkipUpdate() + }, + ) { + Text(text = stringResource(MR.strings.action_ok)) + } + }, + ) + } + InfoScreen( icon = MaterialSymbols.Rounded.Verified, headingText = stringResource(MR.strings.update_check_notification_update_available), @@ -52,6 +84,14 @@ fun NewUpdateScreen( canAccept = stage != NewUpdateScreenModel.Stage.Downloading, rejectText = stringResource(MR.strings.action_not_now), onRejectClick = onRejectUpdate, + rejectAction = { + OutlinedButton(onClick = { showSkipDialog = true }) { + Icon( + imageVector = MaterialSymbols.Rounded.UpdateDisabled, + contentDescription = stringResource(MR.strings.update_check_skip), + ) + } + }, ) { Column( modifier = Modifier @@ -94,6 +134,7 @@ private fun NewUpdateScreenPreview() { onOpenInBrowser = {}, onAcceptUpdate = {}, onRejectUpdate = {}, + onSkipUpdate = {}, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index a7cfee26b..b0eca8d37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -355,7 +355,10 @@ class MainActivity : BaseActivity() { if (updaterEnabled) { try { val result = context.appGraph.updateChecker.checkForUpdate() - if (result is GetApplicationRelease.Result.NewUpdate) { + if ( + result is GetApplicationRelease.Result.NewUpdate && + result.release.version != preferences.skippedAppUpdate.get() + ) { val updateScreen = NewUpdateScreen( versionName = result.release.version, changelogInfo = result.release.info, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt index c6027210d..38c6bae82 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt @@ -43,6 +43,10 @@ class NewUpdateScreen( } }, onRejectUpdate = navigator::pop, + onSkipUpdate = { + viewModel.skipUpdate(versionName) + navigator.pop() + }, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreenModel.kt index 9454bf359..218a9c452 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreenModel.kt @@ -12,6 +12,7 @@ import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metrox.viewmodel.ManualViewModelAssistedFactory import dev.zacsweers.metrox.viewmodel.ManualViewModelAssistedFactoryKey +import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper @@ -38,6 +39,7 @@ class NewUpdateScreenModel( @Assisted private val downloadLink: String, private val context: Context, private val network: NetworkHelper, + private val preferences: BasePreferences, ) : ViewModel() { val state: StateFlow @@ -96,6 +98,11 @@ class NewUpdateScreenModel( response.body.source().saveTo(apkFile) } + fun skipUpdate(versionName: String) { + downloadJob?.cancel() + preferences.skippedAppUpdate.set(versionName) + } + fun installUpdate() { val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(apkFile.getUriCompat(context), ExtensionInstaller.APK_MIME) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index 0c50f6eb4..48b93e1a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -53,6 +53,7 @@ class CrashLogUtil( App ID: ${BuildConfig.APPLICATION_ID} App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}, ${BuildConfig.BUILD_TIME}) Installation ID: ${preferences.installationId.get()} + Skipped app update: ${preferences.skippedAppUpdate.get().ifEmpty { "None" }} Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}; build ${Build.DISPLAY}) Device brand: ${Build.BRAND} Device manufacturer: ${Build.MANUFACTURER} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 6ec72a80d..19925c456 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -993,6 +993,9 @@ Download Open on GitHub + Skip this version + Skip %s? + Updates often contain bug fixes and security improvements. You won\'t be reminded about this version again, but you\'ll still be notified when a newer one is released. This Android version is no longer supported No new updates available diff --git a/icons/material-symbols/src/main/valkyrieResources/rounded/update_disabled.svg b/icons/material-symbols/src/main/valkyrieResources/rounded/update_disabled.svg new file mode 100644 index 000000000..2e26a1b68 --- /dev/null +++ b/icons/material-symbols/src/main/valkyrieResources/rounded/update_disabled.svg @@ -0,0 +1,6 @@ + + diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt index e67dafa6b..9c495bd32 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt @@ -1,9 +1,12 @@ package tachiyomi.presentation.core.screens import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -42,6 +45,7 @@ fun InfoScreen( canAccept: Boolean = true, rejectText: String? = null, onRejectClick: (() -> Unit)? = null, + rejectAction: (@Composable RowScope.() -> Unit)? = null, content: @Composable ColumnScope.() -> Unit, ) { Scaffold( @@ -73,11 +77,14 @@ fun InfoScreen( Text(text = acceptText) } if (rejectText != null && onRejectClick != null) { - OutlinedButton( - modifier = Modifier.fillMaxWidth(), - onClick = onRejectClick, - ) { - Text(text = rejectText) + Row(horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall)) { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = onRejectClick, + ) { + Text(text = rejectText) + } + rejectAction?.invoke(this) } } }