-
-
Notifications
You must be signed in to change notification settings - Fork 17
Code Restructuring and Consistent Bottom Bar Added #289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dab3914
d04c3b2
3db2124
4bae1f0
893775b
add8d94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| [*.{kt,kts}] | ||
| ktlint_function_naming_ignore_when_annotated_with = Composable | ||
| compose_allowed_composition_locals = LocalNavigator,LocalSharedText |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,36 +6,43 @@ import android.os.Bundle | |||||||||||||||||||||||||||||||||
| import androidx.activity.ComponentActivity | ||||||||||||||||||||||||||||||||||
| import androidx.activity.compose.setContent | ||||||||||||||||||||||||||||||||||
| import androidx.activity.enableEdgeToEdge | ||||||||||||||||||||||||||||||||||
| import androidx.compose.foundation.layout.Column | ||||||||||||||||||||||||||||||||||
| import androidx.compose.animation.AnimatedVisibility | ||||||||||||||||||||||||||||||||||
| import androidx.compose.animation.slideInVertically | ||||||||||||||||||||||||||||||||||
| import androidx.compose.animation.slideOutVertically | ||||||||||||||||||||||||||||||||||
| import androidx.compose.foundation.layout.WindowInsets | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.BottomAppBar | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.BottomAppBarDefaults | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.ExperimentalMaterial3Api | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.Icon | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.NavigationBarItem | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.Scaffold | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.Surface | ||||||||||||||||||||||||||||||||||
| import androidx.compose.material3.Text | ||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.Composable | ||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.CompositionLocalProvider | ||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.compositionLocalOf | ||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.getValue | ||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.mutableStateListOf | ||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.remember | ||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.Modifier | ||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.hapticfeedback.HapticFeedbackType | ||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.platform.LocalHapticFeedback | ||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.platform.LocalLayoutDirection | ||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.res.stringResource | ||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator | ||||||||||||||||||||||||||||||||||
| import androidx.navigation3.runtime.NavEntry | ||||||||||||||||||||||||||||||||||
| import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator | ||||||||||||||||||||||||||||||||||
| import androidx.navigation3.ui.NavDisplay | ||||||||||||||||||||||||||||||||||
| import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.AboutUs | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.AboutUsScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.BackupScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.BackupScreenContent | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServer | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServerScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.RestoreScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.RestoreScreenContent | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.BaseScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.LocalNavigator | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.Screen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.TopLevelBackStack | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.TopLevelRoute | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.Settings | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.SettingsScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.TransferLinkLocalNetworkServer | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.TransferLinkLocalServerScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.home.Home | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.home.HomeScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.home.Dashboard2 | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.home.TagSelectionScreen | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.theme.DeeprTheme | ||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.util.LanguageUtil | ||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||||||||||||||||||||||||||||||||
|
|
@@ -81,7 +88,9 @@ class MainActivity : ComponentActivity() { | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| setContent { | ||||||||||||||||||||||||||||||||||
| val preferenceDataStore = remember { AppPreferenceDataStore(this) } | ||||||||||||||||||||||||||||||||||
| val themeMode by preferenceDataStore.getThemeMode.collectAsStateWithLifecycle(initialValue = "system") | ||||||||||||||||||||||||||||||||||
| val themeMode by preferenceDataStore.getThemeMode.collectAsStateWithLifecycle( | ||||||||||||||||||||||||||||||||||
| initialValue = "system", | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| DeeprTheme(themeMode = themeMode) { | ||||||||||||||||||||||||||||||||||
| Surface { | ||||||||||||||||||||||||||||||||||
|
|
@@ -123,70 +132,96 @@ class MainActivity : ComponentActivity() { | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| private val TOP_LEVEL_ROUTES: List<TopLevelRoute> = | ||||||||||||||||||||||||||||||||||
| listOf(Dashboard2(), TagSelectionScreen, Settings) | ||||||||||||||||||||||||||||||||||
yogeshpaliyal marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| val LocalSharedText = | ||||||||||||||||||||||||||||||||||
| compositionLocalOf<Pair<SharedLink?, () -> Unit>?> { null } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @OptIn(ExperimentalMaterial3Api::class) | ||||||||||||||||||||||||||||||||||
| @Composable | ||||||||||||||||||||||||||||||||||
| fun Dashboard( | ||||||||||||||||||||||||||||||||||
| modifier: Modifier = Modifier, | ||||||||||||||||||||||||||||||||||
| sharedText: SharedLink? = null, | ||||||||||||||||||||||||||||||||||
| resetSharedText: () -> Unit, | ||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||
| val backStack = remember(sharedText) { mutableStateListOf<Any>(Home) } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Column(modifier = modifier) { | ||||||||||||||||||||||||||||||||||
| NavDisplay( | ||||||||||||||||||||||||||||||||||
| backStack = backStack, | ||||||||||||||||||||||||||||||||||
| entryDecorators = | ||||||||||||||||||||||||||||||||||
| listOf( | ||||||||||||||||||||||||||||||||||
| // Add the default decorators for managing scenes and saving state | ||||||||||||||||||||||||||||||||||
| rememberSceneSetupNavEntryDecorator(), | ||||||||||||||||||||||||||||||||||
| rememberSavedStateNavEntryDecorator(), | ||||||||||||||||||||||||||||||||||
| // Then add the view model store decorator | ||||||||||||||||||||||||||||||||||
| rememberViewModelStoreNavEntryDecorator(), | ||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||
| onBack = { backStack.removeLastOrNull() }, | ||||||||||||||||||||||||||||||||||
| entryProvider = { key -> | ||||||||||||||||||||||||||||||||||
| when (key) { | ||||||||||||||||||||||||||||||||||
| is Home -> | ||||||||||||||||||||||||||||||||||
| NavEntry(key) { | ||||||||||||||||||||||||||||||||||
| HomeScreen( | ||||||||||||||||||||||||||||||||||
| backStack, | ||||||||||||||||||||||||||||||||||
| sharedText = sharedText, | ||||||||||||||||||||||||||||||||||
| resetSharedText = resetSharedText, | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| is Settings -> | ||||||||||||||||||||||||||||||||||
| NavEntry(key) { | ||||||||||||||||||||||||||||||||||
| SettingsScreen(backStack) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| is AboutUs -> | ||||||||||||||||||||||||||||||||||
| NavEntry(key) { | ||||||||||||||||||||||||||||||||||
| AboutUsScreen(backStack) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| is LocalNetworkServer -> | ||||||||||||||||||||||||||||||||||
| NavEntry(key) { | ||||||||||||||||||||||||||||||||||
| LocalNetworkServerScreen(backStack) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| is TransferLinkLocalNetworkServer -> | ||||||||||||||||||||||||||||||||||
| NavEntry(key) { | ||||||||||||||||||||||||||||||||||
| TransferLinkLocalServerScreen(backStack) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| is BackupScreen -> | ||||||||||||||||||||||||||||||||||
| NavEntry(key) { | ||||||||||||||||||||||||||||||||||
| BackupScreenContent(backStack) | ||||||||||||||||||||||||||||||||||
| val backStack = | ||||||||||||||||||||||||||||||||||
| remember { | ||||||||||||||||||||||||||||||||||
| TopLevelBackStack<BaseScreen>( | ||||||||||||||||||||||||||||||||||
| Dashboard2(), | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| val current = backStack.getLast() | ||||||||||||||||||||||||||||||||||
| val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior() | ||||||||||||||||||||||||||||||||||
| val hapticFeedback = LocalHapticFeedback.current | ||||||||||||||||||||||||||||||||||
| val layoutDirection = LocalLayoutDirection.current | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| CompositionLocalProvider(LocalSharedText provides Pair(sharedText, resetSharedText)) { | ||||||||||||||||||||||||||||||||||
| CompositionLocalProvider(LocalNavigator provides backStack) { | ||||||||||||||||||||||||||||||||||
| Scaffold( | ||||||||||||||||||||||||||||||||||
| modifier = modifier, | ||||||||||||||||||||||||||||||||||
| bottomBar = { | ||||||||||||||||||||||||||||||||||
| AnimatedVisibility( | ||||||||||||||||||||||||||||||||||
| (TOP_LEVEL_ROUTES.any { it::class == current::class }), | ||||||||||||||||||||||||||||||||||
| enter = slideInVertically(initialOffsetY = { it }), | ||||||||||||||||||||||||||||||||||
| exit = slideOutVertically(targetOffsetY = { it }), | ||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||
| BottomAppBar(scrollBehavior = scrollBehavior) { | ||||||||||||||||||||||||||||||||||
| TOP_LEVEL_ROUTES.forEach { topLevelRoute -> | ||||||||||||||||||||||||||||||||||
| val isSelected = | ||||||||||||||||||||||||||||||||||
| topLevelRoute::class == backStack.topLevelKey::class | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+165
to
+172
|
||||||||||||||||||||||||||||||||||
| (TOP_LEVEL_ROUTES.any { it::class == current::class }), | |
| enter = slideInVertically(initialOffsetY = { it }), | |
| exit = slideOutVertically(targetOffsetY = { it }), | |
| ) { | |
| BottomAppBar(scrollBehavior = scrollBehavior) { | |
| TOP_LEVEL_ROUTES.forEach { topLevelRoute -> | |
| val isSelected = | |
| topLevelRoute::class == backStack.topLevelKey::class | |
| (TOP_LEVEL_ROUTES.any { it.id == current.id }), | |
| enter = slideInVertically(initialOffsetY = { it }), | |
| exit = slideOutVertically(targetOffsetY = { it }), | |
| ) { | |
| BottomAppBar(scrollBehavior = scrollBehavior) { | |
| TOP_LEVEL_ROUTES.forEach { topLevelRoute -> | |
| val isSelected = | |
| topLevelRoute.id == backStack.topLevelKey.id |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,92 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.yogeshpaliyal.deepr.ui | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.foundation.layout.WindowInsets | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.Composable | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.compositionLocalOf | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.getValue | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.mutableStateListOf | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.mutableStateOf | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.setValue | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.runtime.snapshots.SnapshotStateList | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.graphics.vector.ImageVector | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation3.runtime.NavKey | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.yogeshpaliyal.deepr.ui.screens.home.Dashboard2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.collections.remove | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.collections.remove |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential memory leak: The LocalNavigator composition local is initialized with a default TopLevelBackStack(Dashboard2()) that's never used. This creates an unnecessary instance. Consider using a nullable type or throwing an error if accessed without being provided.
| compositionLocalOf<TopLevelBackStack<BaseScreen>> { TopLevelBackStack(Dashboard2()) } | |
| compositionLocalOf<TopLevelBackStack<BaseScreen>> { error("LocalNavigator not provided") } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the TopLevelBackStack class. This is a core navigation component that manages the app's navigation state and should have comprehensive KDoc explaining its purpose, behavior, and usage patterns.
| /** | |
| * Core navigation component that manages the app's navigation state for top-level routes. | |
| * | |
| * This class maintains a separate navigation stack for each top-level route (such as tabs or main sections). | |
| * When navigating to a new top-level route, it either creates a new stack or restores the existing one, | |
| * allowing users to preserve their navigation history within each section. | |
| * | |
| * The [TopLevelBackStack] exposes methods to add new routes, switch between top-level routes, | |
| * and manage the back stack. It is designed to be used with Compose's CompositionLocal system | |
| * (see [LocalNavigator]) to provide navigation state throughout the app. | |
| * | |
| * Typical usage: | |
| * ``` | |
| * val navigator = TopLevelBackStack(Dashboard2()) | |
| * navigator.addTopLevel(SettingsScreen()) | |
| * navigator.add(DetailsScreen()) | |
| * navigator.removeLast() | |
| * ``` | |
| * | |
| * @param T The type representing navigation keys/screens. | |
| * @property startKey The initial top-level route. | |
| */ |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linkedMapOf constructor is used but the generic type for the map values is SnapshotStateList<T>. This could cause type safety issues. Consider explicitly declaring the type: LinkedHashMap<T, SnapshotStateList<T>>() for better clarity.
| linkedMapOf( | |
| startKey to mutableStateListOf(startKey), | |
| ) | |
| LinkedHashMap<T, SnapshotStateList<T>>().apply { | |
| put(startKey, mutableStateListOf(startKey)) | |
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method clearStackAndAdd clears all navigation stacks but doesn't check if the backStack is empty before adding the new key. If navigation state relies on having at least one item, this could cause issues. Consider documenting this behavior or adding a safeguard.
| addTopLevel(key) | |
| addTopLevel(key) | |
| // Safeguard: Ensure backStack is not empty after operation | |
| check(backStack.isNotEmpty()) { "Navigation backStack must not be empty after clearStackAndAdd. Key: $key" } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removeLast() method could throw an exception if the backStack is empty. Consider adding a null safety check or returning a boolean to indicate success/failure, similar to how removeLastOrNull() was used in the old code.
| // If the removed key was a top level key, remove the associated top level stack | |
| topLevelStacks.remove(removedKey) | |
| topLevelKey = topLevelStacks.keys.last() | |
| if (removedKey != null) { | |
| // If the removed key was a top level key, remove the associated top level stack | |
| topLevelStacks.remove(removedKey) | |
| } | |
| if (topLevelStacks.isNotEmpty()) { | |
| topLevelKey = topLevelStacks.keys.last() | |
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null pointer exception: topLevelStacks.keys.last() on line 89 can throw NoSuchElementException if topLevelStacks becomes empty after removing the last key. Add a check to prevent navigation state from becoming invalid.
| topLevelKey = topLevelStacks.keys.last() | |
| if (topLevelStacks.isNotEmpty()) { | |
| topLevelKey = topLevelStacks.keys.last() | |
| } |
Uh oh!
There was an error while loading. Please reload this page.