From ad7fc35495f965ca2e846fb161f4bb0a48ae3e0e Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:05:01 -0700 Subject: [PATCH] feat: OverlayManager --- .../aliucord/manager/ManagerApplication.kt | 4 +- .../manager/manager/OverlayManager.kt | 81 +++++++++++++++++++ .../patcher/steps/install/InstallStep.kt | 27 ++----- .../ui/screens/patching/PatchingScreen.kt | 5 -- .../screens/patching/PatchingScreenModel.kt | 25 ------ 5 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/manager/OverlayManager.kt diff --git a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt index 87a2ea7..749b3b8 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt @@ -5,8 +5,7 @@ import com.aliucord.manager.di.* import com.aliucord.manager.domain.repository.AliucordMavenRepository import com.aliucord.manager.domain.repository.GithubRepository import com.aliucord.manager.installers.pm.PMInstaller -import com.aliucord.manager.manager.InstallerManager -import com.aliucord.manager.manager.PathManager +import com.aliucord.manager.manager.* import com.aliucord.manager.manager.download.AndroidDownloadManager import com.aliucord.manager.manager.download.KtorDownloadManager import com.aliucord.manager.network.service.* @@ -71,6 +70,7 @@ class ManagerApplication : Application() { single { providePreferences() } singleOf(::PathManager) singleOf(::InstallerManager) + singleOf(::OverlayManager) singleOf(::DownloadManagerProvider) singleOf(::AndroidDownloadManager) diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/OverlayManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/OverlayManager.kt new file mode 100644 index 0000000..9148732 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/manager/OverlayManager.kt @@ -0,0 +1,81 @@ +package com.aliucord.manager.manager + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ComposeView +import com.aliucord.manager.di.ActivityProvider +import com.aliucord.manager.ui.components.ManagerTheme +import com.aliucord.manager.ui.components.Theme +import kotlinx.coroutines.* +import kotlin.coroutines.resume + +typealias ResultComposable = @Composable (onResult: (R) -> Unit) -> Unit + +/** + * This is used to display dialogs on top of the current activity at any point in the code. + * The main use case for this is dialogs for which a result is needed during patching steps. + * The only other alternative is binding `Flow`s from the steps back to the patching screen model + * and then displaying them in the UI, which is too much boilerplate. + */ +class OverlayManager( + private val prefs: PreferencesManager, + private val activityProvider: ActivityProvider, +) { + /** + * Attaches a new [ComposeView] content view to the root of the activity on top of everything else. + * This view will be displayed until the `onResult` callback is called, + * after which this method will finish suspending with the result from the callback invoke. + */ + suspend fun startComposableForResult(newDialog: ResultComposable): R { + return suspendCancellableCoroutine { continuation -> + val activity = activityProvider.get() + var view: View? = null + + val callback = callback@{ value: R -> + val v = view ?: return@callback + + CoroutineScope(Dispatchers.Main).launch { + (v.parent as ViewGroup).removeView(v) + view = null + } + + continuation.resume(value) + } + + continuation.invokeOnCancellation { + CoroutineScope(Dispatchers.Main).launch { + val v = view ?: return@launch + + (v.parent as ViewGroup).removeView(v) + view = null + } + } + + view = ComposeView(activity).apply { + setContent { + ManagerTheme( + isDarkTheme = prefs.theme == Theme.DARK || prefs.theme == Theme.SYSTEM && isSystemInDarkTheme(), + isDynamicColor = prefs.dynamicColor + ) { + newDialog(callback) + } + } + } + + CoroutineScope(Dispatchers.Main).launch { + activity.addContentView( + view!!, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + ) + + view?.bringToFront() + } + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InstallStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InstallStep.kt index 14f0f83..efa146a 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InstallStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InstallStep.kt @@ -4,8 +4,7 @@ import android.content.Context import androidx.lifecycle.* import com.aliucord.manager.R import com.aliucord.manager.installers.InstallerResult -import com.aliucord.manager.manager.InstallerManager -import com.aliucord.manager.manager.PreferencesManager +import com.aliucord.manager.manager.* import com.aliucord.manager.patcher.StepRunner import com.aliucord.manager.patcher.steps.StepGroup import com.aliucord.manager.patcher.steps.base.Step @@ -16,10 +15,12 @@ import com.aliucord.manager.ui.screens.patchopts.PatchOptions import com.aliucord.manager.ui.util.InstallNotifications import com.aliucord.manager.util.isPackageInstalled import com.aliucord.manager.util.isPlayProtectEnabled -import kotlinx.coroutines.flow.* import org.koin.core.component.KoinComponent import org.koin.core.component.inject +/** + * ID used for showing ready notifications if the activity is currently minimized when having reached this step. + */ private const val READY_NOTIF_ID = 200001 /** @@ -29,21 +30,11 @@ class InstallStep(private val options: PatchOptions) : Step(), KoinComponent { private val context: Context by inject() private val installers: InstallerManager by inject() private val prefs: PreferencesManager by inject() - - /** - * Locks and unlocks the step's execution to start and wait until [PlayProtectDialog] dismissed. - */ - private val _gppWarningLock = MutableSharedFlow() - val gppWarningLock: Flow - get() = _gppWarningLock + private val overlays: OverlayManager by inject() override val group = StepGroup.Install override val localizedName = R.string.patch_step_install - suspend fun dismissGPPWarning() { - _gppWarningLock.emit(false) - } - override suspend fun execute(container: StepRunner) { val apk = container.getStep().patchedApk @@ -62,11 +53,9 @@ class InstallStep(private val options: PatchOptions) : Step(), KoinComponent { // Show [PlayProtectDialog] and wait until it gets dismissed if (!context.isPackageInstalled(options.packageName) && context.isPlayProtectEnabled() == true) { - _gppWarningLock.emit(true) - _gppWarningLock - .filter { show -> !show } - .take(1) - .collect() + overlays.startComposableForResult { callback -> + PlayProtectDialog(onDismiss = { callback(Unit) }) + } } val result = installers.getActiveInstaller().waitInstall( diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreen.kt index 8e1a9d8..238c5ac 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreen.kt @@ -32,7 +32,6 @@ import com.aliucord.manager.patcher.steps.StepGroup import com.aliucord.manager.ui.TextBanner import com.aliucord.manager.ui.components.* import com.aliucord.manager.ui.components.dialogs.InstallerAbortDialog -import com.aliucord.manager.ui.components.dialogs.PlayProtectDialog import com.aliucord.manager.ui.screens.patching.components.* import com.aliucord.manager.ui.screens.patchopts.PatchOptions import com.aliucord.manager.ui.util.paddings.* @@ -117,10 +116,6 @@ class PatchingScreen(private val data: PatchOptions) : Screen, Parcelable { BackHandler(onBack = onTryExit) } - if (model.showGppWarning) { - PlayProtectDialog(onDismiss = model::dismissGPPWarning) - } - Scaffold( topBar = { PatchingAppBar(onTryExit) }, ) { paddingValues -> diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt index e375dda..1e742a6 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt @@ -25,7 +25,6 @@ import com.aliucord.manager.util.* import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* import java.text.SimpleDateFormat import java.util.Date import kotlin.time.Duration.Companion.seconds @@ -45,9 +44,6 @@ class PatchingScreenModel( var steps by mutableStateOf>?>(null) private set - var showGppWarning by mutableStateOf(false) - private set - @get:StringRes var funFact by mutableIntStateOf(0) private set @@ -98,20 +94,6 @@ class PatchingScreenModel( application.showToast(R.string.action_cleared_cache) } - /** - * Hide the 'Google Play Protect is enabled on your device' warning dialog - */ - fun dismissGPPWarning() { - showGppWarning = false - - // Continue executing step - screenModelScope.launch { - stepRunner - ?.getStep(completed = false) - ?.dismissGPPWarning() - } - } - fun restart() { runnerJob?.cancel("Manual cancellation") steps = null @@ -136,13 +118,6 @@ class PatchingScreenModel( val runner = KotlinPatchRunner(options) .also { stepRunner = it } - // Bind InstallStep's GPP Warning state to this - runner.getStep(completed = false) - .gppWarningLock - .take(1) // Take only the initially trigger value - .onEach { showGppWarning = true } - .launchIn(CoroutineScope(currentCoroutineContext())) - steps = runner.steps.groupBy { it.group } .mapValues { it.value.toUnsafeImmutable() } .toUnsafeImmutable()