fix: Handle version check for AboutController

This commit is contained in:
Ahmad Ansori Palembani 2025-01-03 09:26:49 +07:00
parent eba5aa1d2e
commit e06b28a60e
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
4 changed files with 90 additions and 49 deletions

View file

@ -117,6 +117,7 @@ import eu.kanade.tachiyomi.util.system.prepareSideNavContext
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.tryTakePersistableUriPermission
import eu.kanade.tachiyomi.util.system.withUIContext
import eu.kanade.tachiyomi.util.view.BackHandlerControllerInterface
import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.blurBehindWindow
@ -136,12 +137,10 @@ import kotlin.collections.set
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.roundToLong
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
import yokai.core.migration.Migrator
import yokai.domain.base.BasePreferences
@ -198,6 +197,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
dimenW to dimenH
}
@Deprecated("Create contract directly from Composable")
private val requestNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (!isGranted) {
@ -1001,7 +1001,6 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
}
private fun checkForAppUpdates() {
// FIXME: Show Compose version of NewUpdateDialog for AboutController
if (isUpdaterEnabled && router.backstack.lastOrNull()?.controller !is AboutController) {
lifecycleScope.launchIO {
try {
@ -1012,7 +1011,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
val isBeta = result.release.preRelease == true
// Create confirmation window
withContext(Dispatchers.Main) {
withUIContext {
showNotificationPermissionPrompt()
AppUpdateNotifier.releasePageUrl = result.release.releaseLink
AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router)

View file

@ -21,23 +21,18 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import eu.kanade.tachiyomi.R
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
import yokai.i18n.MR
import yokai.util.lang.getString
import dev.icerock.moko.resources.compose.stringResource
import yokai.presentation.component.Gap
import yokai.presentation.theme.Size
@ -53,35 +48,26 @@ internal class PermissionStep : OnboardingStep {
@Composable
override fun Content() {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner.lifecycle) {
val observer = object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
installGranted =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.packageManager.canRequestPackageInstalls()
} else {
@Suppress("DEPRECATION")
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.INSTALL_NON_MARKET_APPS
) != 0
} || context.isShizukuInstalled
notificationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
} else {
true
}
batteryGranted = context.getSystemService<PowerManager>()!!
.isIgnoringBatteryOptimizations(context.packageName)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
installGranted =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.packageManager.canRequestPackageInstalls()
} else {
@Suppress("DEPRECATION")
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.INSTALL_NON_MARKET_APPS
) != 0
} || context.isShizukuInstalled
notificationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
} else {
true
}
batteryGranted = context.getSystemService<PowerManager>()!!
.isIgnoringBatteryOptimizations(context.packageName)
}
Column(

View file

@ -10,14 +10,17 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.material.textview.MaterialTextView
import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
import eu.kanade.tachiyomi.ui.more.parseReleaseNotes
import java.io.Serializable
import kotlin.coroutines.resume
import yokai.domain.DialogHostState
import yokai.i18n.MR
import android.R as AR
data class NewUpdateData(
val body: String,
@ -89,3 +92,17 @@ private fun MarkdownText(text: String) {
},
)
}
suspend fun DialogHostState.awaitNotificationPermissionDeniedDialog(): Unit = dialog { cont ->
// cont.resume(Unit) so that new update dialog will be shown next
AlertDialog(
onDismissRequest = { if (cont.isActive) cont.resume(Unit) },
title = { Text(text = stringResource(MR.strings.warning)) },
text = { Text(text = stringResource(MR.strings.allow_notifications_recommended)) },
confirmButton = {
TextButton(onClick = { if (cont.isActive) cont.resume(Unit) }) {
Text(text = stringResource(AR.string.ok))
}
},
)
}

View file

@ -4,6 +4,8 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
@ -25,8 +27,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import cafe.adriel.voyager.navigator.LocalNavigator
import co.touchlab.kermit.Logger
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.core.storage.preference.asDateFormat
@ -39,7 +44,9 @@ import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.compose.LocalDialogHostState
import eu.kanade.tachiyomi.util.compose.currentOrThrow
import eu.kanade.tachiyomi.util.lang.toTimestampString
import eu.kanade.tachiyomi.util.showNotificationPermissionPrompt
import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.withUIContext
@ -49,7 +56,8 @@ import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import kotlinx.coroutines.launch
import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.domain.DialogHostState
import yokai.i18n.MR
import yokai.presentation.component.preference.widget.TextPreferenceWidget
@ -70,11 +78,34 @@ class AboutScreen : Screen() {
val dialogHostState = LocalDialogHostState.currentOrThrow
val uriHandler = LocalUriHandler.current
val preferences = remember { Injekt.get<PreferencesHelper>() }
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val preferences: PreferencesHelper by injectLazy()
val requestNotificationPermission = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (!isGranted) {
scope.launch { dialogHostState.awaitNotificationPermissionDeniedDialog() }
}
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
// FIXME: Move this to MainActivity once the app is fully migrated to Compose
scope.launchIO {
context.checkVersion(
dialogState = dialogHostState,
isUserPrompt = false,
notificationPrompt = {
context.showNotificationPermissionPrompt(
requestNotificationPermission,
false,
preferences,
)
}
)
}
}
val dateFormat by lazy { preferences.dateFormatRaw().get().asDateFormat() }
SettingsScaffold(
@ -108,7 +139,7 @@ class AboutScreen : Screen() {
onPreferenceClick = {
if (context.isOnline()) {
scope.launch {
context.checkVersion(dialogHostState)
context.checkVersion(dialogHostState, true)
}
} else {
context.toast(MR.strings.no_network_connection)
@ -192,16 +223,25 @@ class AboutScreen : Screen() {
else -> "Release ${BuildConfig.VERSION_NAME}"
}
private suspend fun Context.checkVersion(dialogState: DialogHostState) {
private fun Context.toastIfNotUserPrompt(message: StringResource, isUserPrompt: Boolean) {
toastIfNotUserPrompt(getString(message), isUserPrompt)
}
private fun Context.toastIfNotUserPrompt(message: String?, isUserPrompt: Boolean) {
if (!isUserPrompt) return
toast(message)
}
private suspend fun Context.checkVersion(dialogState: DialogHostState, isUserPrompt: Boolean, notificationPrompt: () -> Unit = {}) {
val updateChecker = AppUpdateChecker()
withUIContext { toast(MR.strings.searching_for_updates) }
withUIContext { toastIfNotUserPrompt(MR.strings.searching_for_updates, isUserPrompt) }
val result = try {
updateChecker.checkForUpdate(this, true)
updateChecker.checkForUpdate(this, isUserPrompt)
} catch (error: Exception) {
withUIContext {
toast(error.message)
toastIfNotUserPrompt(error.message, isUserPrompt)
Logger.e(error) { "Couldn't check new update" }
}
}
@ -215,14 +255,13 @@ class AboutScreen : Screen() {
// Create confirmation window
withUIContext {
if (!isUserPrompt) { notificationPrompt() }
AppUpdateNotifier.releasePageUrl = result.release.releaseLink
dialogState.awaitNewUpdateDialog(data)
}
}
is AppUpdateResult.NoNewUpdate -> {
withUIContext {
toast(MR.strings.no_new_updates_available)
}
withUIContext { toastIfNotUserPrompt(MR.strings.no_new_updates_available, isUserPrompt) }
}
}
}