From 75abb23b70acb17a470b40a094e0831168173d75 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 24 Dec 2025 02:28:08 +0530 Subject: [PATCH] Refine microG install logic & ui states --- .../data/receiver/InstallerStatusReceiver.kt | 2 +- .../com/aurora/store/util/FlavouredUtil.kt | 2 +- .../aurora/store/compose/composable/MicroG.kt | 40 +++++++++--- .../store/compose/ui/details/MicroGScreen.kt | 6 +- .../store/compose/ui/onboarding/MicroGPage.kt | 28 ++++++-- .../compose/ui/onboarding/OnboardingScreen.kt | 39 ++++++----- .../viewmodel/onboarding/MicroGViewModel.kt | 64 ++++++++++++++++--- .../onboarding/OnboardingViewModel.kt | 48 ++++++++++++++ settings.gradle.kts | 4 +- 9 files changed, 186 insertions(+), 47 deletions(-) diff --git a/app/src/huawei/java/com/aurora/store/data/receiver/InstallerStatusReceiver.kt b/app/src/huawei/java/com/aurora/store/data/receiver/InstallerStatusReceiver.kt index 2bb55e67c..a7e0d64fd 100644 --- a/app/src/huawei/java/com/aurora/store/data/receiver/InstallerStatusReceiver.kt +++ b/app/src/huawei/java/com/aurora/store/data/receiver/InstallerStatusReceiver.kt @@ -58,7 +58,7 @@ class InstallerStatusReceiver : BaseInstallerStatusReceiver() { } } - override fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) { + override fun postStatus(status: Int, packageName: String, extra: String?, context: Context) { super.postStatus(status, packageName, extra, context) if (::apiClient.isInitialized && apiClient.isConnected) { diff --git a/app/src/huawei/java/com/aurora/store/util/FlavouredUtil.kt b/app/src/huawei/java/com/aurora/store/util/FlavouredUtil.kt index 62a58bae9..db60dd444 100644 --- a/app/src/huawei/java/com/aurora/store/util/FlavouredUtil.kt +++ b/app/src/huawei/java/com/aurora/store/util/FlavouredUtil.kt @@ -5,7 +5,7 @@ import com.aurora.extensions.isHuawei object FlavouredUtil : IFlavouredUtil { - override val defaultDispensers = emptySet() + override val defaultDispensers : Set = emptySet() override fun promptMicroGInstall(context: Context): Boolean { return isHuawei && diff --git a/app/src/main/java/com/aurora/store/compose/composable/MicroG.kt b/app/src/main/java/com/aurora/store/compose/composable/MicroG.kt index 2eb0b94cb..60eb3cede 100644 --- a/app/src/main/java/com/aurora/store/compose/composable/MicroG.kt +++ b/app/src/main/java/com/aurora/store/compose/composable/MicroG.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Checkbox +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -32,21 +33,22 @@ import com.aurora.extensions.browse import com.aurora.store.R import com.aurora.store.compose.preview.PreviewTemplate import com.aurora.store.data.model.Link +import com.aurora.store.viewmodel.onboarding.MicroGUIState /** * Composable to display suggestion to install microG * @param modifier Modifier for the composable - * @param isTOSChecked Whether the TOS checkbox is checked * @param onInstall Callback when user requests installing microG bundle */ @Composable fun MicroG( modifier: Modifier = Modifier, - isTOSChecked: Boolean = false, - onInstall: () -> Unit = {} + uiState: MicroGUIState, + onInstall: () -> Unit = {}, + onCheckedChange: (Boolean) -> Unit = {} ) { val context = LocalContext.current - var isChecked by rememberSaveable { mutableStateOf(isTOSChecked) } + var isChecked by rememberSaveable { mutableStateOf(false) } val links = listOf( Link( id = 2, @@ -101,16 +103,22 @@ fun MicroG( modifier = Modifier .fillMaxWidth() .padding(vertical = dimensionResource(R.dimen.padding_xlarge)), - horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small)) ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( dimensionResource(R.dimen.padding_small) - ) + ), ) { - Checkbox(checked = isChecked, onCheckedChange = { isChecked = !isChecked }) + Checkbox( + checked = isChecked, + onCheckedChange = { + isChecked = it + onCheckedChange(it) + }, + enabled = !uiState.isInstalled && !uiState.isDownloading + ) Text( text = stringResource(R.string.onboarding_gms_agreement), style = MaterialTheme.typography.bodyMedium, @@ -118,9 +126,21 @@ fun MicroG( overflow = TextOverflow.Ellipsis ) } - Button(onClick = onInstall, enabled = isChecked) { + Button( + onClick = onInstall, + enabled = isChecked && !uiState.isDownloading && !uiState.isInstalled, + modifier = Modifier.fillMaxWidth() + ) { Text( - text = stringResource(R.string.action_install_microG) + text = if (uiState.isDownloading) + stringResource(R.string.action_installing) + else + stringResource(R.string.action_install_microG) + ) + } + if (uiState.isDownloading) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth() ) } } @@ -131,6 +151,6 @@ fun MicroG( @Composable private fun MicroGPreview() { PreviewTemplate { - MicroG(isTOSChecked = true) + MicroG(uiState = MicroGUIState()) } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt index bf6e92c6f..fd1057fdc 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt @@ -42,6 +42,7 @@ import com.aurora.store.compose.preview.PreviewTemplate import com.aurora.store.data.model.PermissionType import com.aurora.store.data.providers.PermissionProvider import com.aurora.store.viewmodel.details.AppDetailsViewModel +import com.aurora.store.viewmodel.onboarding.MicroGUIState import com.aurora.store.viewmodel.onboarding.MicroGViewModel @Composable @@ -61,6 +62,7 @@ fun MicroGScreen( ScreenContent( topAppBarTitle = topAppBarTitle, + uiState = viewModel.uiState, onNavigateUp = onNavigateUp, onInstall = { viewModel.downloadMicroG() }, onIgnore = onIgnore @@ -70,6 +72,7 @@ fun MicroGScreen( @Composable private fun ScreenContent( topAppBarTitle: String? = null, + uiState: MicroGUIState = MicroGUIState(), onNavigateUp: () -> Unit = {}, onInstall: () -> Unit = {}, onIgnore: () -> Unit = {}, @@ -104,7 +107,8 @@ private fun ScreenContent( else -> context.toast(R.string.permissions_denied) } - } + }, + uiState = uiState ) Row( diff --git a/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt b/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt index c3bfe7a5e..617ef61fa 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt @@ -27,17 +27,27 @@ import com.aurora.store.compose.composable.MicroG import com.aurora.store.compose.preview.PreviewTemplate import com.aurora.store.data.model.PermissionType import com.aurora.store.data.providers.PermissionProvider +import com.aurora.store.viewmodel.onboarding.MicroGUIState import com.aurora.store.viewmodel.onboarding.MicroGViewModel @Composable -fun MicroGPage(viewModel: MicroGViewModel = hiltViewModel()) { +fun MicroGPage( + onCheckedChange: (Boolean) -> Unit = {}, + viewModel: MicroGViewModel = hiltViewModel(), +) { ScreenContent( - onInstall = { viewModel.downloadMicroG() } + uiState = viewModel.uiState, + onInstall = { viewModel.downloadMicroG() }, + onMicrogTOCChange = onCheckedChange, ) } @Composable -private fun ScreenContent(onInstall: () -> Unit = {}) { +private fun ScreenContent( + uiState: MicroGUIState, + onInstall: () -> Unit = {}, + onMicrogTOCChange: (Boolean) -> Unit = {} +) { val context = LocalContext.current Column( @@ -71,9 +81,13 @@ private fun ScreenContent(onInstall: () -> Unit = {}) { onInstall() } - else -> context.toast(R.string.permissions_denied) + else -> context.toast( + R.string.permissions_denied + ) } - } + }, + uiState = uiState, + onCheckedChange = onMicrogTOCChange ) } @@ -83,6 +97,8 @@ private fun ScreenContent(onInstall: () -> Unit = {}) { @Composable private fun MicroGPagePreview() { PreviewTemplate { - ScreenContent() + ScreenContent( + uiState = MicroGUIState(), + ) } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt index b1003e2c2..99a495b1b 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -37,22 +36,25 @@ import com.aurora.store.compose.composable.Logo import com.aurora.store.compose.composable.PageIndicator import com.aurora.store.compose.preview.PreviewTemplate import com.aurora.store.compose.ui.onboarding.navigation.OnboardingPage -import com.aurora.store.util.FlavouredUtil +import com.aurora.store.viewmodel.onboarding.OnboardingUiState import com.aurora.store.viewmodel.onboarding.OnboardingViewModel import kotlinx.coroutines.launch @Composable fun OnboardingScreen(viewModel: OnboardingViewModel = hiltViewModel()) { - val context = LocalContext.current val pages = listOfNotNull( OnboardingPage.WELCOME, OnboardingPage.PERMISSIONS, - if (FlavouredUtil.promptMicroGInstall(context)) OnboardingPage.MICRO_G else null + if (viewModel.isMicroGPromptRequired) OnboardingPage.MICRO_G else null ) ScreenContent( pages = pages, - onFinishOnboarding = { viewModel.finishOnboarding() } + onFinishOnboarding = { + viewModel.finishOnboarding() + }, + uiState = viewModel.uiState, + onCheckedChange = viewModel::onMicroGCheckedChange ) } @@ -60,6 +62,8 @@ fun OnboardingScreen(viewModel: OnboardingViewModel = hiltViewModel()) { private fun ScreenContent( pages: List = emptyList(), onFinishOnboarding: () -> Unit = {}, + uiState: OnboardingUiState, + onCheckedChange: (Boolean) -> Unit, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() ) { val pagerState = rememberPagerState { pages.size } @@ -70,10 +74,10 @@ private fun ScreenContent( } @Composable - fun SetupActions() { + fun SetupActions(uiState: OnboardingUiState) { val horizontalButtonPadding = when { windowAdaptiveInfo.isWindowCompact -> dimensionResource(R.dimen.padding_medium) - else -> dimensionResource(R.dimen.padding_xlarge) + else -> dimensionResource(R.dimen.padding_xlarge) } Row( @@ -91,14 +95,14 @@ private fun ScreenContent( ) { val buttonWidthModifier = when { windowAdaptiveInfo.isWindowCompact -> Modifier.weight(1F) - else -> Modifier.widthIn(min = dimensionResource(R.dimen.width_button)) + else -> Modifier.widthIn(min = dimensionResource(R.dimen.width_button)) } TextButton( modifier = buttonWidthModifier, onClick = { when (pagerState.currentPage) { - 0 -> onFinishOnboarding() + 0 -> onFinishOnboarding() else -> { coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage - 1) @@ -109,7 +113,7 @@ private fun ScreenContent( ) { Text( text = when (pagerState.currentPage) { - 0 -> stringResource(R.string.action_skip) + 0 -> stringResource(R.string.action_skip) else -> stringResource(R.string.action_back) }, maxLines = 1, @@ -119,10 +123,11 @@ private fun ScreenContent( Button( modifier = buttonWidthModifier, + enabled = !isFinalPage() || (!uiState.isMicroBundleChecked || uiState.isMicroGBundleInstalled), onClick = { when { isFinalPage() -> onFinishOnboarding() - else -> { + else -> { coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } @@ -133,7 +138,7 @@ private fun ScreenContent( Text( text = when { isFinalPage() -> stringResource(R.string.action_finish) - else -> stringResource(R.string.action_next) + else -> stringResource(R.string.action_next) }, maxLines = 1, overflow = TextOverflow.Ellipsis @@ -173,14 +178,14 @@ private fun ScreenContent( verticalAlignment = Alignment.Top ) { page -> when (pages[page]) { - OnboardingPage.WELCOME -> WelcomePage() + OnboardingPage.WELCOME -> WelcomePage() OnboardingPage.PERMISSIONS -> PermissionsPage() - OnboardingPage.MICRO_G -> MicroGPage() + OnboardingPage.MICRO_G -> MicroGPage(onCheckedChange = onCheckedChange) } } } - SetupActions() + SetupActions(uiState) } } } @@ -190,7 +195,9 @@ private fun ScreenContent( private fun OnboardingScreenPreview() { PreviewTemplate { ScreenContent( - pages = listOf(OnboardingPage.WELCOME, OnboardingPage.PERMISSIONS) + pages = listOf(OnboardingPage.WELCOME, OnboardingPage.PERMISSIONS), + uiState = OnboardingUiState(), + onCheckedChange = {} ) } } diff --git a/app/src/main/java/com/aurora/store/viewmodel/onboarding/MicroGViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/onboarding/MicroGViewModel.kt index 9a85cb7fc..b34e646fb 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/onboarding/MicroGViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/onboarding/MicroGViewModel.kt @@ -5,31 +5,60 @@ package com.aurora.store.viewmodel.onboarding +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.Constants.PACKAGE_NAME_GMS import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE import com.aurora.gplayapi.data.models.PlayFile +import com.aurora.store.AuroraApp +import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.helper.DownloadHelper import com.aurora.store.data.room.suite.ExternalApk +import com.aurora.store.util.PackageUtil import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject +data class MicroGUIState( + var isDownloading: Boolean = false, + var isInstalled: Boolean = false +) + @HiltViewModel class MicroGViewModel @Inject constructor( + @ApplicationContext + private val context: Context, private val downloadHelper: DownloadHelper ) : ViewModel() { companion object { private const val MICROG_DOWNLOAD_URL = "https://github.com/microg/GmsCore/releases/download" - private const val MICROG_VERSION = "v0.3.6.244735" - private const val GMS_VERSION_CODE = 244735012 - private const val COMPANION_VERSION_CODE = 84022612 + private const val MICROG_VERSION = "v0.3.11.250932" + private const val GMS_VERSION_CODE = 250932022 + private const val COMPANION_VERSION_CODE = 84022622 } + init { + AuroraApp.events.installerEvent.onEach { + when (it) { + is InstallerEvent.Installed -> confirmBundleInstall(it.packageName) + else -> {} + } + }.launchIn(AuroraApp.scope) + } + + var uiState: MicroGUIState by mutableStateOf(MicroGUIState()) + private set + private val microGServiceApk = ExternalApk( packageName = PACKAGE_NAME_GMS, versionCode = GMS_VERSION_CODE.toLong(), @@ -41,8 +70,8 @@ class MicroGViewModel @Inject constructor( PlayFile( url = "$MICROG_DOWNLOAD_URL/$MICROG_VERSION/$PACKAGE_NAME_GMS-$GMS_VERSION_CODE-hw.apk", name = "$PACKAGE_NAME_GMS-$GMS_VERSION_CODE-hw.apk", - size = 32509431, - sha256 = "2f14df2974811b576bfafa6167a97e3b3032f2bd6e6ec3887a833fd2fa350dda" + size = 92386474, + sha256 = "2894a93544a8d7ca8f6ca96e7cc697647a7e0862165b6a02f8cd26822759b9cc" ) ) ) @@ -58,16 +87,33 @@ class MicroGViewModel @Inject constructor( PlayFile( url = "$MICROG_DOWNLOAD_URL/$MICROG_VERSION/$PACKAGE_NAME_PLAY_STORE-$COMPANION_VERSION_CODE-hw.apk", name = "$PACKAGE_NAME_PLAY_STORE-$COMPANION_VERSION_CODE-hw.apk", - size = 3915551, - sha256 = "6835b09016cef0fc3469b4a36b1720427ad3f81161cf20b188f0dadb5f8594e1" + size = 4626291, + sha256 = "b9623b8da8791c7e887efca941434b20e517c8a42ca4fda713625957edcc84eb" ) ) ) fun downloadMicroG() { viewModelScope.launch(Dispatchers.IO) { - downloadHelper.enqueueStandalone(microGServiceApk) - downloadHelper.enqueueStandalone(microGCompanionApk) + if (microGCompanionApk.isInstalled(context)) { + AuroraApp.events.send(InstallerEvent.Installed(PACKAGE_NAME_PLAY_STORE)) + } else { + downloadHelper.enqueueStandalone(microGCompanionApk) + uiState = uiState.copy(isDownloading = true) + } + + if (microGServiceApk.isInstalled(context)) { + AuroraApp.events.send(InstallerEvent.Installed(PACKAGE_NAME_GMS)) + } else { + downloadHelper.enqueueStandalone(microGServiceApk) + uiState = uiState.copy(isDownloading = true) + } + } + } + + fun confirmBundleInstall(packageName: String) { + if (PackageUtil.isMicroGBundleInstalled(context)) { + uiState = uiState.copy(isInstalled = true, isDownloading = false) } } } diff --git a/app/src/main/java/com/aurora/store/viewmodel/onboarding/OnboardingViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/onboarding/OnboardingViewModel.kt index bba0298f2..99c0059ac 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/onboarding/OnboardingViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/onboarding/OnboardingViewModel.kt @@ -7,13 +7,23 @@ package com.aurora.store.viewmodel.onboarding import android.content.Context import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import com.aurora.Constants.PACKAGE_NAME_GMS +import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE import com.aurora.extensions.areNotificationsEnabled import com.aurora.extensions.isIgnoringBatteryOptimizations +import com.aurora.store.AuroraApp +import com.aurora.store.BuildConfig +import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.helper.UpdateHelper import com.aurora.store.data.model.UpdateMode +import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.data.work.CacheWorker import com.aurora.store.util.FlavouredUtil +import com.aurora.store.util.PackageUtil import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB @@ -32,19 +42,51 @@ import com.aurora.store.util.save import com.jakewharton.processphoenix.ProcessPhoenix import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject +data class OnboardingUiState( + val isMicroBundleChecked: Boolean = false, + val isMicroGBundleInstalled: Boolean = false +) + @HiltViewModel class OnboardingViewModel @Inject constructor( val updateHelper: UpdateHelper, + val blacklistProvider: BlacklistProvider, @ApplicationContext private val context: Context ) : ViewModel() { private val TAG = OnboardingViewModel::class.java.simpleName + val isMicroGPromptRequired = FlavouredUtil.promptMicroGInstall(context) + + var uiState by mutableStateOf(OnboardingUiState()) + private set + + fun onMicroGCheckedChange(value: Boolean) { + uiState = uiState.copy(isMicroBundleChecked = value) + } + + init { + AuroraApp.events.installerEvent.onEach { + when (it) { + is InstallerEvent.Installed -> confirmBundleInstall() + else -> {} + } + }.launchIn(AuroraApp.scope) + } + fun finishOnboarding() { Log.i(TAG, "Finishing onboarding with defaults") context.saveDefaultPreferences() + + if (BuildConfig.FLAVOR == "huawei") { + blacklistProvider.blacklist(PACKAGE_NAME_GMS) + blacklistProvider.blacklist(PACKAGE_NAME_PLAY_STORE) + } + setupAutoUpdates() CacheWorker.scheduleAutomatedCacheCleanup(context) Preferences.putBooleanNow(context, PREFERENCE_INTRO, true) @@ -53,6 +95,12 @@ class OnboardingViewModel @Inject constructor( ProcessPhoenix.triggerRebirth(context) } + private fun confirmBundleInstall() { + if (PackageUtil.isMicroGBundleInstalled(context)) { + uiState = uiState.copy(isMicroGBundleInstalled = true) + } + } + private fun setupAutoUpdates() { val updateMode = when { context.isIgnoringBatteryOptimizations() -> UpdateMode.CHECK_AND_INSTALL diff --git a/settings.gradle.kts b/settings.gradle.kts index b490e3305..9e8322e04 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,9 +24,7 @@ dependencyResolutionManagement { } // Only included in huawei variants maven("https://developer.huawei.com/repo/") { - content { - includeModule("com.huawei.hms", "ag-coreservice") - } + } } }