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.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.tryTakePersistableUriPermission 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.BackHandlerControllerInterface
import eu.kanade.tachiyomi.util.view.backgroundColor import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.blurBehindWindow import eu.kanade.tachiyomi.util.view.blurBehindWindow
@ -136,12 +137,10 @@ import kotlin.collections.set
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToLong import kotlin.math.roundToLong
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.core.migration.Migrator import yokai.core.migration.Migrator
import yokai.domain.base.BasePreferences import yokai.domain.base.BasePreferences
@ -198,6 +197,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
dimenW to dimenH dimenW to dimenH
} }
@Deprecated("Create contract directly from Composable")
private val requestNotificationPermissionLauncher = private val requestNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (!isGranted) { if (!isGranted) {
@ -1001,7 +1001,6 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
} }
private fun checkForAppUpdates() { private fun checkForAppUpdates() {
// FIXME: Show Compose version of NewUpdateDialog for AboutController
if (isUpdaterEnabled && router.backstack.lastOrNull()?.controller !is AboutController) { if (isUpdaterEnabled && router.backstack.lastOrNull()?.controller !is AboutController) {
lifecycleScope.launchIO { lifecycleScope.launchIO {
try { try {
@ -1012,7 +1011,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
val isBeta = result.release.preRelease == true val isBeta = result.release.preRelease == true
// Create confirmation window // Create confirmation window
withContext(Dispatchers.Main) { withUIContext {
showNotificationPermissionPrompt() showNotificationPermissionPrompt()
AppUpdateNotifier.releasePageUrl = result.release.releaseLink AppUpdateNotifier.releasePageUrl = result.release.releaseLink
AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router) 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.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.LocalLifecycleOwner import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.isShizukuInstalled import eu.kanade.tachiyomi.util.system.isShizukuInstalled
import yokai.i18n.MR import yokai.i18n.MR
import yokai.util.lang.getString
import dev.icerock.moko.resources.compose.stringResource
import yokai.presentation.component.Gap import yokai.presentation.component.Gap
import yokai.presentation.theme.Size import yokai.presentation.theme.Size
@ -53,11 +48,8 @@ internal class PermissionStep : OnboardingStep {
@Composable @Composable
override fun Content() { override fun Content() {
val context = LocalContext.current val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner.lifecycle) { LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
val observer = object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
installGranted = installGranted =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.packageManager.canRequestPackageInstalls() context.packageManager.canRequestPackageInstalls()
@ -77,12 +69,6 @@ internal class PermissionStep : OnboardingStep {
batteryGranted = context.getSystemService<PowerManager>()!! batteryGranted = context.getSystemService<PowerManager>()!!
.isIgnoringBatteryOptimizations(context.packageName) .isIgnoringBatteryOptimizations(context.packageName)
} }
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Column( Column(
modifier = Modifier.padding(vertical = Size.medium), modifier = Modifier.padding(vertical = Size.medium),

View file

@ -10,14 +10,17 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
import eu.kanade.tachiyomi.ui.more.parseReleaseNotes import eu.kanade.tachiyomi.ui.more.parseReleaseNotes
import java.io.Serializable import java.io.Serializable
import kotlin.coroutines.resume
import yokai.domain.DialogHostState import yokai.domain.DialogHostState
import yokai.i18n.MR import yokai.i18n.MR
import android.R as AR
data class NewUpdateData( data class NewUpdateData(
val body: String, 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.ClipboardManager
import android.content.Context import android.content.Context
import android.os.Build 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.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow 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.platform.LocalUriHandler
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.core.storage.preference.asDateFormat 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.LocalDialogHostState
import eu.kanade.tachiyomi.util.compose.currentOrThrow import eu.kanade.tachiyomi.util.compose.currentOrThrow
import eu.kanade.tachiyomi.util.lang.toTimestampString 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.isOnline
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.system.withUIContext
@ -49,7 +56,8 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import kotlinx.coroutines.launch 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.domain.DialogHostState
import yokai.i18n.MR import yokai.i18n.MR
import yokai.presentation.component.preference.widget.TextPreferenceWidget import yokai.presentation.component.preference.widget.TextPreferenceWidget
@ -70,11 +78,34 @@ class AboutScreen : Screen() {
val dialogHostState = LocalDialogHostState.currentOrThrow val dialogHostState = LocalDialogHostState.currentOrThrow
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val preferences = remember { Injekt.get<PreferencesHelper>() }
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() 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() } val dateFormat by lazy { preferences.dateFormatRaw().get().asDateFormat() }
SettingsScaffold( SettingsScaffold(
@ -108,7 +139,7 @@ class AboutScreen : Screen() {
onPreferenceClick = { onPreferenceClick = {
if (context.isOnline()) { if (context.isOnline()) {
scope.launch { scope.launch {
context.checkVersion(dialogHostState) context.checkVersion(dialogHostState, true)
} }
} else { } else {
context.toast(MR.strings.no_network_connection) context.toast(MR.strings.no_network_connection)
@ -192,16 +223,25 @@ class AboutScreen : Screen() {
else -> "Release ${BuildConfig.VERSION_NAME}" 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() val updateChecker = AppUpdateChecker()
withUIContext { toast(MR.strings.searching_for_updates) } withUIContext { toastIfNotUserPrompt(MR.strings.searching_for_updates, isUserPrompt) }
val result = try { val result = try {
updateChecker.checkForUpdate(this, true) updateChecker.checkForUpdate(this, isUserPrompt)
} catch (error: Exception) { } catch (error: Exception) {
withUIContext { withUIContext {
toast(error.message) toastIfNotUserPrompt(error.message, isUserPrompt)
Logger.e(error) { "Couldn't check new update" } Logger.e(error) { "Couldn't check new update" }
} }
} }
@ -215,14 +255,13 @@ class AboutScreen : Screen() {
// Create confirmation window // Create confirmation window
withUIContext { withUIContext {
if (!isUserPrompt) { notificationPrompt() }
AppUpdateNotifier.releasePageUrl = result.release.releaseLink AppUpdateNotifier.releasePageUrl = result.release.releaseLink
dialogState.awaitNewUpdateDialog(data) dialogState.awaitNewUpdateDialog(data)
} }
} }
is AppUpdateResult.NoNewUpdate -> { is AppUpdateResult.NoNewUpdate -> {
withUIContext { withUIContext { toastIfNotUserPrompt(MR.strings.no_new_updates_available, isUserPrompt) }
toast(MR.strings.no_new_updates_available)
}
} }
} }
} }