From 91fc4d714a8b601771eb374c6f4f34c4e9e1f1e1 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 20 May 2026 00:53:03 +0530 Subject: [PATCH] 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. --- .../java/com/aurora/store/ComposeActivity.kt | 16 ++- .../java/com/aurora/store/MainViewModel.kt | 2 - .../compose/composition/LocalNetworkStatus.kt | 19 +++ .../store/compose/ui/commons/NetworkScreen.kt | 112 ++++++++++++++++++ .../store/compose/ui/commons/NetworkSheet.kt | 96 --------------- .../store/compose/ui/main/MainScreen.kt | 18 +-- .../store/compose/ui/splash/SplashScreen.kt | 6 +- .../store/viewmodel/auth/AuthViewModel.kt | 2 - 8 files changed, 154 insertions(+), 117 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/compose/composition/LocalNetworkStatus.kt create mode 100644 app/src/main/java/com/aurora/store/compose/ui/commons/NetworkScreen.kt delete mode 100644 app/src/main/java/com/aurora/store/compose/ui/commons/NetworkSheet.kt diff --git a/app/src/main/java/com/aurora/store/ComposeActivity.kt b/app/src/main/java/com/aurora/store/ComposeActivity.kt index ca2d4131a..da6576e03 100644 --- a/app/src/main/java/com/aurora/store/ComposeActivity.kt +++ b/app/src/main/java/com/aurora/store/ComposeActivity.kt @@ -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) } diff --git a/app/src/main/java/com/aurora/store/MainViewModel.kt b/app/src/main/java/com/aurora/store/MainViewModel.kt index 8d70dbf25..8d2d6513e 100644 --- a/app/src/main/java/com/aurora/store/MainViewModel.kt +++ b/app/src/main/java/com/aurora/store/MainViewModel.kt @@ -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() diff --git a/app/src/main/java/com/aurora/store/compose/composition/LocalNetworkStatus.kt b/app/src/main/java/com/aurora/store/compose/composition/LocalNetworkStatus.kt new file mode 100644 index 000000000..46d30dc6d --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/composition/LocalNetworkStatus.kt @@ -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 } diff --git a/app/src/main/java/com/aurora/store/compose/ui/commons/NetworkScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/commons/NetworkScreen.kt new file mode 100644 index 000000000..f14143b5e --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/commons/NetworkScreen.kt @@ -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() +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/commons/NetworkSheet.kt b/app/src/main/java/com/aurora/store/compose/ui/commons/NetworkSheet.kt deleted file mode 100644 index 2631eb993..000000000 --- a/app/src/main/java/com/aurora/store/compose/ui/commons/NetworkSheet.kt +++ /dev/null @@ -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()) - } - } -} diff --git a/app/src/main/java/com/aurora/store/compose/ui/main/MainScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/main/MainScreen.kt index 1976ab6fb..beea1e85a 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/main/MainScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/main/MainScreen.kt @@ -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(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) { diff --git a/app/src/main/java/com/aurora/store/compose/ui/splash/SplashScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/splash/SplashScreen.kt index cab620f00..388cac66e 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/splash/SplashScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/splash/SplashScreen.kt @@ -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) diff --git a/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt index ed8ec0541..428ca10b9 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt @@ -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() {