feat: OverlayManager
This commit is contained in:
parent
193036f37c
commit
ad7fc35495
5 changed files with 91 additions and 51 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue