compose: rewire navigation as the single entry point

ComposeActivity becomes the sole launch target; the legacy MainActivity
is removed. Destination / Screen pair drives navigation3, NavDisplay
handles deep links plus microG-aware resume rechecks, and Theme.kt
is tightened for edge-to-edge.
This commit is contained in:
Rahul Patel
2026-05-19 12:10:59 +05:30
parent 7617a9b953
commit baf593bd51
7 changed files with 301 additions and 278 deletions

View File

@@ -82,9 +82,9 @@
tools:targetApi="tiramisu">
<activity
android:name=".MainActivity"
android:name=".ComposeActivity"
android:exported="true"
android:windowSoftInputMode="adjustPan">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -103,16 +103,28 @@
<intent-filter>
<action android:name="android.intent.action.SHOW_APP_INFO" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<nav-graph android:value="@navigation/mobile_navigation" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="market" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="market.android.com" />
<data android:host="play.google.com" />
<data android:host="play.google.com" />
</intent-filter>
</activity>
<!-- Activity to host composable screens -->
<activity
android:name=".ComposeActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<!-- Notification Action (Download Complete) -->
<activity
android:name=".data.activity.InstallActivity"

View File

@@ -6,6 +6,7 @@
package com.aurora.store
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -18,31 +19,22 @@ 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.receiver.MigrationReceiver
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
MigrationReceiver.runMigrationsIfRequired(this)
enableEdgeToEdge()
super.onCreate(savedInstanceState)
// Ensure the classloader is set for the Screen parcelable
intent.setExtrasClassLoader(Screen::class.java.classLoader)
var startDestination = IntentCompat.getParcelableExtra(
intent,
Screen.PARCEL_KEY,
Screen::class.java
) ?: Screen.Onboarding
// If the intent contains a package name for app details,
// Override the start destination to be the app details screen for that package.
val packageName = intent.getPackageName()
if (packageName != null) {
startDestination = Screen.AppDetails(packageName)
}
val startDestination = resolveStartDestination()
val localUI = when {
PackageUtil.isTv(this) -> UI.TV
@@ -57,4 +49,32 @@ class ComposeActivity : ComponentActivity() {
}
}
}
private fun resolveStartDestination(): Screen {
// Parcel-based navigation (e.g. from NotificationUtil)
IntentCompat.getParcelableExtra(intent, Screen.PARCEL_KEY, Screen::class.java)
?.let { return it }
// Deep links via ACTION_VIEW
if (intent.action == Intent.ACTION_VIEW) {
val data = intent.data
val path = data?.path.orEmpty()
val id = data?.getQueryParameter("id")
return when {
id != null && path.contains("/apps/dev") -> Screen.DevProfile(id)
id != null -> Screen.AppDetails(id)
else -> defaultStart()
}
}
// SEND / SHOW_APP_INFO — getPackageName() handles both
intent.getPackageName()?.let { return Screen.AppDetails(it) }
return defaultStart()
}
private fun defaultStart(): Screen = when {
!Preferences.getBoolean(this, Preferences.PREFERENCE_INTRO) -> Screen.Onboarding
else -> Screen.Splash
}
}

View File

@@ -1,157 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
* Copyright (C) 2022, The Calyx Institute
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store
import android.os.Bundle
import android.view.View
import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat.Type.displayCutout
import androidx.core.view.WindowInsetsCompat.Type.ime
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.lifecycle.lifecycleScope
import androidx.navigation.FloatingWindow
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.aurora.store.data.model.NetworkStatus
import com.aurora.store.data.receiver.MigrationReceiver
import com.aurora.store.databinding.ActivityMainBinding
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.view.ui.sheets.NetworkDialogSheet
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var binding: ActivityMainBinding
// TopLevelFragments
private val topLevelFrags = listOf(
R.id.appsContainerFragment,
R.id.gamesContainerFragment,
R.id.updatesFragment
)
override fun onCreate(savedInstanceState: Bundle?) {
// Check and run migrations first if required
// This is needed thanks to OEMs breaking the MY_PACKAGE_REPLACED API
MigrationReceiver.runMigrationsIfRequired(this)
enableEdgeToEdge()
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Adjust root view's paddings for edgeToEdge display
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { root, windowInsets ->
val insets = windowInsets.getInsets(systemBars() or displayCutout() or ime())
root.setPadding(insets.left, insets.top, insets.right, 0)
windowInsets
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
if (!PackageUtil.isTv(this)) {
viewModel.networkProvider.status.onEach { networkStatus ->
when (networkStatus) {
NetworkStatus.AVAILABLE -> {
if (!supportFragmentManager.isDestroyed && isIntroDone()) {
val fragment = supportFragmentManager
.findFragmentByTag(NetworkDialogSheet.TAG)
fragment?.let {
supportFragmentManager.beginTransaction()
.remove(fragment)
.commitAllowingStateLoss()
}
}
}
NetworkStatus.UNAVAILABLE -> {
if (!supportFragmentManager.isDestroyed && isIntroDone()) {
supportFragmentManager.beginTransaction()
.add(NetworkDialogSheet.newInstance(), NetworkDialogSheet.TAG)
.commitAllowingStateLoss()
}
}
}
}.launchIn(AuroraApp.scope)
}
binding.navView.setupWithNavController(navController)
// Handle quick exit from back actions
val defaultTab = when (Preferences.getInteger(this, PREFERENCE_DEFAULT_SELECTED_TAB)) {
1 -> R.id.gamesContainerFragment
2 -> R.id.updatesFragment
else -> R.id.appsContainerFragment
}
onBackPressedDispatcher.addCallback(this) {
if (navController.currentDestination?.id in topLevelFrags) {
if (navController.currentDestination?.id == defaultTab) {
finish()
} else {
navController.navigate(defaultTab)
}
} else if (navHostFragment.childFragmentManager.backStackEntryCount == 0) {
// We are on either on onboarding or splash fragment
finish()
} else {
navController.navigateUp()
}
}
// Handle views on fragments
navController.addOnDestinationChangedListener { _, navDestination, _ ->
if (navDestination !is FloatingWindow) {
when (navDestination.id) {
in topLevelFrags -> binding.navView.visibility = View.VISIBLE
else -> binding.navView.visibility = View.GONE
}
}
}
// Updates
lifecycleScope.launch {
viewModel.updateHelper.updates.collectLatest { list ->
binding.navView.getOrCreateBadge(R.id.updatesFragment).apply {
isVisible = !list.isNullOrEmpty()
number = list?.size ?: 0
}
}
}
}
private fun isIntroDone(): Boolean = Preferences.getBoolean(this, Preferences.PREFERENCE_INTRO)
}

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2026 Aurora OSS
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.navigation
import com.aurora.gplayapi.data.models.Category
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.store.data.model.MinimalApp
import com.aurora.store.data.model.PermissionType
/**
* All navigation actions available to composable screens.
* Screens emit one of these via a single `onNavigateTo: (Destination) -> Unit` callback.
*/
sealed class Destination {
data object Splash : Destination()
data class Main(val initialTab: Int) : Destination()
data class AppDetails(val packageName: String) : Destination()
data class DevProfile(val devId: String) : Destination()
data class AppMenu(val app: MinimalApp) : Destination()
data object Search : Destination()
data object Downloads : Destination()
data class StreamBrowse(val cluster: StreamCluster) : Destination()
data class ExpandedStreamBrowse(val title: String, val browseUrl: String) : Destination()
data class CategoryBrowse(val category: Category) : Destination()
data class PermissionRationale(val permissions: Set<PermissionType>) : Destination()
data object Accounts : Destination()
data object GoogleLogin : Destination()
data object About : Destination()
data object Favourite : Destination()
data object Spoof : Destination()
data object Installed : Destination()
data object Blacklist : Destination()
data object Settings : Destination()
data object InstallationPreference : Destination()
data object Installer : Destination()
data object NetworkPreference : Destination()
data object Dispenser : Destination()
data object UIPreference : Destination()
data object UpdatesPreference : Destination()
}

View File

@@ -7,16 +7,23 @@
package com.aurora.store.compose.navigation
import android.content.Intent
import androidx.activity.compose.LocalActivity
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation.NavDeepLinkBuilder
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.metadata
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
@@ -24,11 +31,13 @@ import com.aurora.Constants.PACKAGE_NAME_GMS
import com.aurora.extensions.toast
import com.aurora.store.AuroraApp
import com.aurora.store.ComposeActivity
import com.aurora.store.MainActivity
import com.aurora.store.R
import com.aurora.store.compose.ui.about.AboutScreen
import com.aurora.store.compose.ui.accounts.AccountsScreen
import com.aurora.store.compose.ui.accounts.GoogleLoginScreen
import com.aurora.store.compose.ui.blacklist.BlacklistScreen
import com.aurora.store.compose.ui.commons.CategoryBrowseScreen
import com.aurora.store.compose.ui.commons.ExpandedStreamBrowseScreen
import com.aurora.store.compose.ui.commons.PermissionRationaleScreen
import com.aurora.store.compose.ui.commons.StreamBrowseScreen
import com.aurora.store.compose.ui.details.AppDetailsScreen
@@ -37,9 +46,16 @@ import com.aurora.store.compose.ui.dispenser.DispenserScreen
import com.aurora.store.compose.ui.downloads.DownloadsScreen
import com.aurora.store.compose.ui.favourite.FavouriteScreen
import com.aurora.store.compose.ui.installed.InstalledScreen
import com.aurora.store.compose.ui.main.MainScreen
import com.aurora.store.compose.ui.onboarding.OnboardingScreen
import com.aurora.store.compose.ui.preferences.SettingsScreen
import com.aurora.store.compose.ui.preferences.UIPreferenceScreen
import com.aurora.store.compose.ui.preferences.installation.InstallationPreferenceScreen
import com.aurora.store.compose.ui.preferences.installation.InstallerScreen
import com.aurora.store.compose.ui.preferences.network.NetworkPreferenceScreen
import com.aurora.store.compose.ui.preferences.updates.UpdatesPreferenceScreen
import com.aurora.store.compose.ui.search.SearchScreen
import com.aurora.store.compose.ui.splash.SplashScreen
import com.aurora.store.compose.ui.spoof.SpoofScreen
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.model.AccountType
@@ -54,23 +70,6 @@ import com.aurora.store.util.Preferences
@Composable
fun NavDisplay(startDestination: NavKey) {
val backstack = rememberNavBackStack(startDestination)
// TODO: Rework when migrating splash fragment to compose
val splashIntent = NavDeepLinkBuilder(LocalContext.current)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.splashFragment)
.setComponentName(MainActivity::class.java)
.createTaskStackBuilder()
.intents
.first()
.apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) }
// TODO: Drop this logic once everything is in compose
val activity = LocalActivity.current
fun onNavigateUp() {
if (backstack.size == 1) activity?.finish() else backstack.removeLastOrNull()
}
val context = LocalContext.current
fun isMicroGAuthInvalidated(): Boolean =
@@ -101,114 +100,169 @@ fun NavDisplay(startDestination: NavKey) {
}
}
fun navigate(destination: Destination) {
when (destination) {
Destination.Splash -> {
// Clear the backstack when navigating to Splash to prevent going back to the previous screen when the user is sent back to the splash screen (e.g. after logout).
backstack.clear()
backstack.add(Screen.Splash)
}
is Destination.Main -> {
// Clear the backstack when navigating to Main to prevent going back to the splash screen or other screens.
backstack.clear()
backstack.add(Screen.Main(destination.initialTab))
}
is Destination.ExpandedStreamBrowse -> backstack.add(
Screen.ExpandedStreamBrowse(destination.title, destination.browseUrl)
)
is Destination.CategoryBrowse -> backstack.add(
Screen.CategoryBrowse(destination.category.title, destination.category.browseUrl)
)
is Destination.PermissionRationale -> backstack.add(
Screen.PermissionRationale(destination.permissions)
)
is Destination.AppDetails -> backstack.add(Screen.AppDetails(destination.packageName))
is Destination.DevProfile -> backstack.add(Screen.DevProfile(destination.devId))
is Destination.AppMenu -> Unit
is Destination.StreamBrowse -> backstack.add(Screen.StreamBrowse(destination.cluster))
Destination.Search -> backstack.add(Screen.Search)
Destination.Downloads -> backstack.add(Screen.Downloads)
Destination.Accounts -> backstack.add(Screen.Accounts)
Destination.GoogleLogin -> backstack.add(Screen.GoogleLogin)
Destination.About -> backstack.add(Screen.About)
Destination.Favourite -> backstack.add(Screen.Favourite)
Destination.Spoof -> backstack.add(Screen.Spoof)
Destination.Installed -> backstack.add(Screen.Installed)
Destination.Blacklist -> backstack.add(Screen.Blacklist)
Destination.Settings -> backstack.add(Screen.Settings)
Destination.InstallationPreference -> backstack.add(Screen.InstallationPreference)
Destination.Installer -> backstack.add(Screen.Installer)
Destination.NetworkPreference -> backstack.add(Screen.NetworkPreference)
Destination.Dispenser -> backstack.add(Screen.Dispenser)
Destination.UIPreference -> backstack.add(Screen.UIPreference)
Destination.UpdatesPreference -> backstack.add(Screen.UpdatesPreference)
}
}
NavDisplay(
onBack = { backstack.removeLastOrNull() },
backStack = backstack,
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator()
),
transitionSpec = {
slideInHorizontally(
animationSpec = spring(dampingRatio = 0.8f, stiffness = 380f),
initialOffsetX = { it }
) togetherWith slideOutHorizontally(targetOffsetX = { -it })
},
popTransitionSpec = {
slideInHorizontally(
animationSpec = spring(dampingRatio = 0.8f, stiffness = 380f),
initialOffsetX = { -it }
) togetherWith slideOutHorizontally(targetOffsetX = { it })
},
predictivePopTransitionSpec = {
slideInHorizontally(
animationSpec = spring(dampingRatio = 0.8f, stiffness = 380f),
initialOffsetX = { -it }
) togetherWith slideOutHorizontally(targetOffsetX = { it })
},
entryProvider = entryProvider {
entry<Screen.Blacklist> {
BlacklistScreen(onNavigateUp = ::onNavigateUp)
}
entry<Screen.Search> {
SearchScreen(onNavigateUp = ::onNavigateUp)
entry<Screen.Main> { screen ->
MainScreen(
initialTab = screen.initialTab,
onNavigateTo = ::navigate
)
}
entry<Screen.AppDetails> { screen ->
AppDetailsScreen(
packageName = screen.packageName,
onNavigateUp = ::onNavigateUp,
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
onNavigateTo = ::navigate
)
}
entry<Screen.DevProfile> { screen ->
DevProfileScreen(
developerId = screen.developerId,
onNavigateUp = ::onNavigateUp,
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
onNavigateTo = ::navigate
)
}
entry<Screen.PermissionRationale> { screen ->
PermissionRationaleScreen(
onNavigateUp = ::onNavigateUp,
requiredPermissions = screen.requiredPermissions
)
}
entry<Screen.Downloads> {
DownloadsScreen(
onNavigateUp = ::onNavigateUp,
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
)
}
entry<Screen.Accounts> {
AccountsScreen(
onNavigateUp = ::onNavigateUp,
onNavigateToSplash = { activity?.startActivity(splashIntent) }
)
}
entry<Screen.About> {
AboutScreen(onNavigateUp = ::onNavigateUp)
}
entry<Screen.Favourite> {
FavouriteScreen(
onNavigateUp = ::onNavigateUp,
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
)
}
entry<Screen.Onboarding> {
OnboardingScreen()
}
entry<Screen.Spoof> {
SpoofScreen(
onNavigateUp = ::onNavigateUp,
onNavigateToSplash = { activity?.startActivity(splashIntent) }
)
}
entry<Screen.Dispenser> {
DispenserScreen(onNavigateUp = ::onNavigateUp)
}
entry<Screen.Installer> {
InstallerScreen(onNavigateUp = ::onNavigateUp)
}
entry<Screen.Installed> {
InstalledScreen(
onNavigateUp = ::onNavigateUp,
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
)
}
entry<Screen.StreamBrowse> { screen ->
StreamBrowseScreen(
streamCluster = screen.streamCluster,
onNavigateUp = ::onNavigateUp,
onNavigateToAppDetails = { packageName ->
backstack.add(Screen.AppDetails(packageName))
}
onNavigateTo = ::navigate
)
}
entry<Screen.ExpandedStreamBrowse> { screen ->
ExpandedStreamBrowseScreen(
browseUrl = screen.browseUrl,
defaultTitle = screen.title,
onNavigateTo = ::navigate
)
}
entry<Screen.CategoryBrowse> { screen ->
CategoryBrowseScreen(
title = screen.title,
browseUrl = screen.browseUrl,
onNavigateTo = ::navigate
)
}
entry<Screen.InstallationPreference> {
InstallationPreferenceScreen(onNavigateTo = ::navigate)
}
entry<Screen.Search>(
metadata = metadata {
put(NavDisplay.TransitionKey) {
fadeIn() togetherWith
ExitTransition.KeepUntilTransitionsFinished
}
put(NavDisplay.PopTransitionKey) {
EnterTransition.None togetherWith
slideOutVertically(targetOffsetY = { it })
}
put(NavDisplay.PredictivePopTransitionKey) {
EnterTransition.None togetherWith
slideOutVertically(targetOffsetY = { it })
}
}
) { SearchScreen() }
entry<Screen.Splash> { SplashScreen(onNavigateTo = ::navigate) }
entry<Screen.Onboarding> { OnboardingScreen() }
entry<Screen.Blacklist> { BlacklistScreen() }
entry<Screen.Downloads> { DownloadsScreen(onNavigateTo = ::navigate) }
entry<Screen.Accounts> { AccountsScreen(onNavigateTo = ::navigate) }
entry<Screen.GoogleLogin> { GoogleLoginScreen(onNavigateTo = ::navigate) }
entry<Screen.About> { AboutScreen() }
entry<Screen.Favourite> { FavouriteScreen(onNavigateTo = ::navigate) }
entry<Screen.Spoof> { SpoofScreen(onNavigateTo = ::navigate) }
entry<Screen.Dispenser> { DispenserScreen() }
entry<Screen.Installer> { InstallerScreen() }
entry<Screen.Installed> { InstalledScreen(onNavigateTo = ::navigate) }
entry<Screen.Settings> { SettingsScreen(onNavigateTo = ::navigate) }
entry<Screen.NetworkPreference> { NetworkPreferenceScreen(onNavigateTo = ::navigate) }
entry<Screen.UIPreference> { UIPreferenceScreen() }
entry<Screen.UpdatesPreference> { UpdatesPreferenceScreen() }
}
)
}

View File

@@ -44,6 +44,9 @@ sealed class Screen : NavKey, Parcelable {
@Serializable
data object Accounts : Screen()
@Serializable
data object GoogleLogin : Screen()
@Serializable
data object About : Screen()
@@ -67,4 +70,31 @@ sealed class Screen : NavKey, Parcelable {
@Serializable
data class StreamBrowse(val streamCluster: StreamCluster) : Screen()
@Serializable
data class ExpandedStreamBrowse(val title: String, val browseUrl: String) : Screen()
@Serializable
data class CategoryBrowse(val title: String, val browseUrl: String) : Screen()
@Serializable
data object Settings : Screen()
@Serializable
data object InstallationPreference : Screen()
@Serializable
data object NetworkPreference : Screen()
@Serializable
data object UIPreference : Screen()
@Serializable
data object UpdatesPreference : Screen()
@Serializable
data object Splash : Screen()
@Serializable
data class Main(val initialTab: Int = 0) : Screen()
}

View File

@@ -6,6 +6,7 @@
package com.aurora.store.compose.theme
import android.content.SharedPreferences
import android.os.Build
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.isSystemInDarkTheme
@@ -16,7 +17,12 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.colorResource
@@ -31,7 +37,19 @@ import com.aurora.store.util.Preferences
fun AuroraTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val themeStyle = Preferences.getInteger(context, Preferences.PREFERENCE_THEME_STYLE)
var themeStyle by remember {
mutableIntStateOf(Preferences.getInteger(context, Preferences.PREFERENCE_THEME_STYLE))
}
DisposableEffect(Unit) {
val prefs = Preferences.getPrefs(context)
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == Preferences.PREFERENCE_THEME_STYLE) {
themeStyle = Preferences.getInteger(context, Preferences.PREFERENCE_THEME_STYLE)
}
}
prefs.registerOnSharedPreferenceChangeListener(listener)
onDispose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }
}
val isDynamicColorSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val lightScheme = if (isDynamicColorSupported) {
@@ -69,11 +87,9 @@ fun AuroraTheme(content: @Composable () -> Unit) {
val currentActivity = activity ?: return@SideEffect
val window = currentActivity.window
// Transparent system bars
window.statusBarColor = android.graphics.Color.TRANSPARENT
window.navigationBarColor = android.graphics.Color.TRANSPARENT
// Control icon colors explicitly
WindowCompat
.getInsetsController(window, view)
.apply {