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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 }
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user