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:
@@ -82,9 +82,9 @@
|
|||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".ComposeActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
@@ -103,16 +103,28 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SHOW_APP_INFO" />
|
<action android:name="android.intent.action.SHOW_APP_INFO" />
|
||||||
</intent-filter>
|
</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>
|
||||||
|
|
||||||
<!-- Activity to host composable screens -->
|
|
||||||
<activity
|
|
||||||
android:name=".ComposeActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:windowSoftInputMode="adjustResize" />
|
|
||||||
|
|
||||||
<!-- Notification Action (Download Complete) -->
|
<!-- Notification Action (Download Complete) -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".data.activity.InstallActivity"
|
android:name=".data.activity.InstallActivity"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package com.aurora.store
|
package com.aurora.store
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
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.NavDisplay
|
||||||
import com.aurora.store.compose.navigation.Screen
|
import com.aurora.store.compose.navigation.Screen
|
||||||
import com.aurora.store.compose.theme.AuroraTheme
|
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.PackageUtil
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ComposeActivity : ComponentActivity() {
|
class ComposeActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
MigrationReceiver.runMigrationsIfRequired(this)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
// Ensure the classloader is set for the Screen parcelable
|
|
||||||
intent.setExtrasClassLoader(Screen::class.java.classLoader)
|
intent.setExtrasClassLoader(Screen::class.java.classLoader)
|
||||||
|
|
||||||
var startDestination = IntentCompat.getParcelableExtra(
|
val startDestination = resolveStartDestination()
|
||||||
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 localUI = when {
|
val localUI = when {
|
||||||
PackageUtil.isTv(this) -> UI.TV
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -7,16 +7,23 @@
|
|||||||
package com.aurora.store.compose.navigation
|
package com.aurora.store.compose.navigation
|
||||||
|
|
||||||
import android.content.Intent
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.compose.LifecycleEventEffect
|
import androidx.lifecycle.compose.LifecycleEventEffect
|
||||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
|
||||||
import androidx.navigation3.runtime.NavKey
|
import androidx.navigation3.runtime.NavKey
|
||||||
import androidx.navigation3.runtime.entryProvider
|
import androidx.navigation3.runtime.entryProvider
|
||||||
|
import androidx.navigation3.runtime.metadata
|
||||||
import androidx.navigation3.runtime.rememberNavBackStack
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
@@ -24,11 +31,13 @@ import com.aurora.Constants.PACKAGE_NAME_GMS
|
|||||||
import com.aurora.extensions.toast
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.store.AuroraApp
|
import com.aurora.store.AuroraApp
|
||||||
import com.aurora.store.ComposeActivity
|
import com.aurora.store.ComposeActivity
|
||||||
import com.aurora.store.MainActivity
|
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.compose.ui.about.AboutScreen
|
import com.aurora.store.compose.ui.about.AboutScreen
|
||||||
import com.aurora.store.compose.ui.accounts.AccountsScreen
|
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.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.PermissionRationaleScreen
|
||||||
import com.aurora.store.compose.ui.commons.StreamBrowseScreen
|
import com.aurora.store.compose.ui.commons.StreamBrowseScreen
|
||||||
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
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.downloads.DownloadsScreen
|
||||||
import com.aurora.store.compose.ui.favourite.FavouriteScreen
|
import com.aurora.store.compose.ui.favourite.FavouriteScreen
|
||||||
import com.aurora.store.compose.ui.installed.InstalledScreen
|
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.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.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.search.SearchScreen
|
||||||
|
import com.aurora.store.compose.ui.splash.SplashScreen
|
||||||
import com.aurora.store.compose.ui.spoof.SpoofScreen
|
import com.aurora.store.compose.ui.spoof.SpoofScreen
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.model.AccountType
|
import com.aurora.store.data.model.AccountType
|
||||||
@@ -54,23 +70,6 @@ import com.aurora.store.util.Preferences
|
|||||||
@Composable
|
@Composable
|
||||||
fun NavDisplay(startDestination: NavKey) {
|
fun NavDisplay(startDestination: NavKey) {
|
||||||
val backstack = rememberNavBackStack(startDestination)
|
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
|
val context = LocalContext.current
|
||||||
|
|
||||||
fun isMicroGAuthInvalidated(): Boolean =
|
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(
|
NavDisplay(
|
||||||
|
onBack = { backstack.removeLastOrNull() },
|
||||||
backStack = backstack,
|
backStack = backstack,
|
||||||
entryDecorators = listOf(
|
entryDecorators = listOf(
|
||||||
rememberSaveableStateHolderNavEntryDecorator(),
|
rememberSaveableStateHolderNavEntryDecorator(),
|
||||||
rememberViewModelStoreNavEntryDecorator()
|
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 {
|
entryProvider = entryProvider {
|
||||||
entry<Screen.Blacklist> {
|
entry<Screen.Main> { screen ->
|
||||||
BlacklistScreen(onNavigateUp = ::onNavigateUp)
|
MainScreen(
|
||||||
}
|
initialTab = screen.initialTab,
|
||||||
|
onNavigateTo = ::navigate
|
||||||
entry<Screen.Search> {
|
)
|
||||||
SearchScreen(onNavigateUp = ::onNavigateUp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<Screen.AppDetails> { screen ->
|
entry<Screen.AppDetails> { screen ->
|
||||||
AppDetailsScreen(
|
AppDetailsScreen(
|
||||||
packageName = screen.packageName,
|
packageName = screen.packageName,
|
||||||
onNavigateUp = ::onNavigateUp,
|
onNavigateTo = ::navigate
|
||||||
onNavigateToAppDetails = { packageName ->
|
|
||||||
backstack.add(Screen.AppDetails(packageName))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<Screen.DevProfile> { screen ->
|
entry<Screen.DevProfile> { screen ->
|
||||||
DevProfileScreen(
|
DevProfileScreen(
|
||||||
developerId = screen.developerId,
|
developerId = screen.developerId,
|
||||||
onNavigateUp = ::onNavigateUp,
|
onNavigateTo = ::navigate
|
||||||
onNavigateToAppDetails = { packageName ->
|
|
||||||
backstack.add(Screen.AppDetails(packageName))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<Screen.PermissionRationale> { screen ->
|
entry<Screen.PermissionRationale> { screen ->
|
||||||
PermissionRationaleScreen(
|
PermissionRationaleScreen(
|
||||||
onNavigateUp = ::onNavigateUp,
|
|
||||||
requiredPermissions = screen.requiredPermissions
|
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 ->
|
entry<Screen.StreamBrowse> { screen ->
|
||||||
StreamBrowseScreen(
|
StreamBrowseScreen(
|
||||||
streamCluster = screen.streamCluster,
|
streamCluster = screen.streamCluster,
|
||||||
onNavigateUp = ::onNavigateUp,
|
onNavigateTo = ::navigate
|
||||||
onNavigateToAppDetails = { packageName ->
|
)
|
||||||
backstack.add(Screen.AppDetails(packageName))
|
}
|
||||||
}
|
|
||||||
)
|
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() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ sealed class Screen : NavKey, Parcelable {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data object Accounts : Screen()
|
data object Accounts : Screen()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object GoogleLogin : Screen()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object About : Screen()
|
data object About : Screen()
|
||||||
|
|
||||||
@@ -67,4 +70,31 @@ sealed class Screen : NavKey, Parcelable {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class StreamBrowse(val streamCluster: StreamCluster) : Screen()
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package com.aurora.store.compose.theme
|
package com.aurora.store.compose.theme
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.compose.LocalActivity
|
import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
@@ -16,7 +17,12 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
|||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
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.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
@@ -31,7 +37,19 @@ import com.aurora.store.util.Preferences
|
|||||||
fun AuroraTheme(content: @Composable () -> Unit) {
|
fun AuroraTheme(content: @Composable () -> Unit) {
|
||||||
val context = LocalContext.current
|
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 isDynamicColorSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
|
||||||
val lightScheme = if (isDynamicColorSupported) {
|
val lightScheme = if (isDynamicColorSupported) {
|
||||||
@@ -69,11 +87,9 @@ fun AuroraTheme(content: @Composable () -> Unit) {
|
|||||||
val currentActivity = activity ?: return@SideEffect
|
val currentActivity = activity ?: return@SideEffect
|
||||||
val window = currentActivity.window
|
val window = currentActivity.window
|
||||||
|
|
||||||
// Transparent system bars
|
|
||||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
window.navigationBarColor = android.graphics.Color.TRANSPARENT
|
window.navigationBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
|
||||||
// Control icon colors explicitly
|
|
||||||
WindowCompat
|
WindowCompat
|
||||||
.getInsetsController(window, view)
|
.getInsetsController(window, view)
|
||||||
.apply {
|
.apply {
|
||||||
|
|||||||
Reference in New Issue
Block a user