network: hoist status into CompositionLocal and promote sheet to screen

Collect NetworkProvider.status once at the activity root and expose it via
LocalNetworkStatus so any screen can read it without injecting the provider.
Drop the redundant networkProvider field from MainViewModel and AuthViewModel.
NetworkSheet becomes a full-screen NetworkScreen with the same content.
This commit is contained in:
Rahul Patel
2026-05-20 00:53:03 +05:30
parent 0391adf82f
commit 91fc4d714a
8 changed files with 154 additions and 117 deletions

View File

@@ -12,21 +12,29 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.core.content.IntentCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.aurora.extensions.getPackageName
import com.aurora.store.compose.composition.LocalNetworkStatus
import com.aurora.store.compose.composition.LocalUI
import com.aurora.store.compose.composition.UI
import com.aurora.store.compose.navigation.NavDisplay
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.compose.theme.AuroraTheme
import com.aurora.store.data.model.NetworkStatus
import com.aurora.store.data.providers.NetworkProvider
import com.aurora.store.data.receiver.MigrationReceiver
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class ComposeActivity : ComponentActivity() {
@Inject lateinit var networkProvider: NetworkProvider
override fun onCreate(savedInstanceState: Bundle?) {
MigrationReceiver.runMigrationsIfRequired(this)
enableEdgeToEdge()
@@ -42,7 +50,13 @@ class ComposeActivity : ComponentActivity() {
}
setContent {
CompositionLocalProvider(LocalUI provides localUI) {
val networkStatus by networkProvider.status.collectAsStateWithLifecycle(
initialValue = NetworkStatus.AVAILABLE
)
CompositionLocalProvider(
LocalUI provides localUI,
LocalNetworkStatus provides networkStatus
) {
AuroraTheme {
NavDisplay(startDestination = startDestination)
}

View File

@@ -7,12 +7,10 @@ package com.aurora.store
import androidx.lifecycle.ViewModel
import com.aurora.store.data.helper.UpdateHelper
import com.aurora.store.data.providers.NetworkProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
val networkProvider: NetworkProvider,
val updateHelper: UpdateHelper
) : ViewModel()

View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2026 Aurora OSS
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.composition
import androidx.compose.runtime.compositionLocalOf
import com.aurora.store.data.model.NetworkStatus
/**
* CompositionLocal carrying the current device network status. Provided once at the
* activity root from a single [com.aurora.store.data.providers.NetworkProvider] subscription,
* so any screen can read `LocalNetworkStatus.current` without injecting the provider
* or duplicating the flow collection.
*
* Uses [compositionLocalOf] (not static) so only readers recompose on change.
*/
val LocalNetworkStatus = compositionLocalOf { NetworkStatus.AVAILABLE }

View File

@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2026 Aurora OSS
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.commons
import android.content.ActivityNotFoundException
import android.content.Intent
import android.provider.Settings
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import androidx.compose.ui.unit.dp
import com.aurora.extensions.isQAndAbove
import com.aurora.store.R
import com.aurora.store.compose.preview.ThemePreviewProvider
private const val TAG = "NetworkScreen"
/**
* Full-screen "no network" placeholder shown while [com.aurora.store.data.model.NetworkStatus]
* is UNAVAILABLE. Replaces the legacy modal sheet. Auto-dismisses by virtue of its caller
* only rendering it while offline.
*/
@Composable
fun NetworkScreen() {
val context = LocalContext.current
Scaffold { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = dimensionResource(R.dimen.spacing_xlarge)),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(
dimensionResource(R.dimen.spacing_medium)
)
) {
Icon(
painter = painterResource(R.drawable.ic_network),
contentDescription = null,
modifier = Modifier.requiredSize(48.dp)
)
Text(
text = stringResource(R.string.title_no_network),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.check_connectivity),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
if (isQAndAbove) {
context.startActivity(
Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)
)
} else {
context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
}
} catch (_: ActivityNotFoundException) {
Log.i(TAG, "Unable to launch network settings")
try {
context.startActivity(Intent(Settings.ACTION_SETTINGS))
} catch (_: Exception) { }
}
}
) {
Text(stringResource(R.string.action_check))
}
}
}
}
}
@PreviewWrapper(ThemePreviewProvider::class)
@Preview
@Composable
private fun NetworkScreenPreview() {
NetworkScreen()
}

View File

@@ -1,96 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 Aurora OSS
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.commons
import android.content.ActivityNotFoundException
import android.content.Intent
import android.provider.Settings
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aurora.extensions.isQAndAbove
import com.aurora.store.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NetworkSheet() {
val context = LocalContext.current
ModalBottomSheet(
onDismissRequest = {},
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { it != SheetValue.Hidden }
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.spacing_xlarge))
.padding(bottom = dimensionResource(R.dimen.spacing_large)),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
painter = painterResource(R.drawable.ic_network),
contentDescription = null,
modifier = Modifier.requiredSize(48.dp)
)
Spacer(Modifier.padding(top = dimensionResource(R.dimen.spacing_large)))
Text(
text = stringResource(R.string.title_no_network),
style = MaterialTheme.typography.titleLarge
)
Spacer(Modifier.padding(top = dimensionResource(R.dimen.spacing_small)))
Text(
text = stringResource(R.string.check_connectivity),
style = MaterialTheme.typography.bodyMedium
)
Spacer(Modifier.padding(top = dimensionResource(R.dimen.spacing_xlarge)))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
if (isQAndAbove) {
context.startActivity(
Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)
)
} else {
context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
}
} catch (e: ActivityNotFoundException) {
Log.i("NetworkSheet", "Unable to launch network settings")
try {
context.startActivity(Intent(Settings.ACTION_SETTINGS))
} catch (_: Exception) { }
}
}
) {
Text(stringResource(R.string.action_check))
}
Spacer(Modifier.navigationBarsPadding())
}
}
}

View File

@@ -23,7 +23,6 @@ import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -39,10 +38,11 @@ import com.aurora.extensions.requiresObbDir
import com.aurora.store.MainViewModel
import com.aurora.store.R
import com.aurora.store.compose.composable.TopAppBar
import com.aurora.store.compose.composition.LocalNetworkStatus
import com.aurora.store.compose.navigation.Destination
import com.aurora.store.compose.ui.apps.AppsGamesScreen
import com.aurora.store.compose.ui.commons.MoreSheet
import com.aurora.store.compose.ui.commons.NetworkSheet
import com.aurora.store.compose.ui.commons.NetworkScreen
import com.aurora.store.compose.ui.sheets.AppUpdateSheet
import com.aurora.store.compose.ui.updates.UpdatesScreen
import com.aurora.store.data.model.NetworkStatus
@@ -69,9 +69,7 @@ fun MainScreen(
onNavigateTo: (Destination) -> Unit = {}
) {
val context = LocalContext.current
val networkStatus by mainViewModel.networkProvider.status.collectAsStateWithLifecycle(
initialValue = NetworkStatus.AVAILABLE
)
val networkStatus = LocalNetworkStatus.current
val updates by mainViewModel.updateHelper.updates.collectAsStateWithLifecycle(
initialValue = null
)
@@ -89,7 +87,6 @@ fun MainScreen(
var showMoreSheet by remember { mutableStateOf(false) }
var appUpdateTarget by remember { mutableStateOf<Update?>(null) }
var showNetworkDialog by remember { mutableStateOf(false) }
fun handleNavigation(destination: Destination) {
when (destination) {
@@ -98,12 +95,9 @@ fun MainScreen(
}
}
LaunchedEffect(networkStatus) {
showNetworkDialog = networkStatus == NetworkStatus.UNAVAILABLE
}
if (showNetworkDialog) {
NetworkSheet()
if (networkStatus == NetworkStatus.UNAVAILABLE) {
NetworkScreen()
return
}
if (showMoreSheet) {

View File

@@ -59,6 +59,7 @@ import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.compose.composition.LocalNetworkStatus
import com.aurora.store.compose.navigation.Destination
import com.aurora.store.data.model.AuthState
import com.aurora.store.data.model.NetworkStatus
@@ -81,10 +82,7 @@ fun SplashScreen(
val activity = LocalActivity.current as? ComponentActivity
val authState by viewModel.authState.collectAsStateWithLifecycle()
val networkStatus by viewModel.networkProvider.status.collectAsStateWithLifecycle(
initialValue = NetworkStatus.AVAILABLE
)
val isOnline = networkStatus == NetworkStatus.AVAILABLE
val isOnline = LocalNetworkStatus.current == NetworkStatus.AVAILABLE
val canMicroGLogin = PackageUtil.hasSupportedMicroGVariant(context) &&
Preferences.getBoolean(context, Preferences.PREFERENCE_MICROG_AUTH, true)

View File

@@ -34,7 +34,6 @@ import com.aurora.store.data.model.AccountType
import com.aurora.store.data.model.AuthState
import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.providers.NetworkProvider
import com.aurora.store.util.AC2DMTask
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
@@ -51,7 +50,6 @@ import kotlinx.coroutines.launch
@HiltViewModel
class AuthViewModel @Inject constructor(
val authProvider: AuthProvider,
val networkProvider: NetworkProvider,
@ApplicationContext private val context: Context,
private val aC2DMTask: AC2DMTask
) : ViewModel() {