feat: OverlayManager

This commit is contained in:
rushiiMachine 2024-08-05 12:05:01 -07:00
parent 193036f37c
commit ad7fc35495
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
5 changed files with 91 additions and 51 deletions

View file

@ -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)

View file

@ -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<R> = @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 <R> startComposableForResult(newDialog: ResultComposable<R>): R {
return suspendCancellableCoroutine { continuation ->
val activity = activityProvider.get<Activity>()
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()
}
}
}
}

View file

@ -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<Boolean>()
val gppWarningLock: Flow<Boolean>
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<CopyDependenciesStep>().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(

View file

@ -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 ->

View file

@ -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<ImmutableMap<StepGroup, ImmutableList<Step>>?>(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<InstallStep>(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<InstallStep>(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()