Refine microG install logic & ui states

This commit is contained in:
Rahul Patel
2025-12-24 02:28:08 +05:30
parent a68acce413
commit 75abb23b70
9 changed files with 186 additions and 47 deletions

View File

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

View File

@@ -5,7 +5,7 @@ import com.aurora.extensions.isHuawei
object FlavouredUtil : IFlavouredUtil {
override val defaultDispensers = emptySet()
override val defaultDispensers : Set<String> = emptySet()
override fun promptMicroGInstall(context: Context): Boolean {
return isHuawei &&

View File

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

View File

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

View File

@@ -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(),
)
}
}

View File

@@ -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<OnboardingPage> = emptyList(),
onFinishOnboarding: () -> Unit = {},
uiState: OnboardingUiState,
onCheckedChange: (Boolean) -> Unit,
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
) {
val pagerState = rememberPagerState { pages.size }
@@ -70,7 +74,7 @@ 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)
@@ -119,6 +123,7 @@ private fun ScreenContent(
Button(
modifier = buttonWidthModifier,
enabled = !isFinalPage() || (!uiState.isMicroBundleChecked || uiState.isMicroGBundleInstalled),
onClick = {
when {
isFinalPage() -> onFinishOnboarding()
@@ -175,12 +180,12 @@ private fun ScreenContent(
when (pages[page]) {
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 = {}
)
}
}

View File

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

View File

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

View File

@@ -24,9 +24,7 @@ dependencyResolutionManagement {
}
// Only included in huawei variants
maven("https://developer.huawei.com/repo/") {
content {
includeModule("com.huawei.hms", "ag-coreservice")
}
}
}
}