Skip to content

Commit 5788818

Browse files
Code Restructuring and Consistent Bottom Bar Added (#289)
* feat: WIP Refactor navigation handling to use LocalNavigator and update screen structure * feat: WIP Refactor navigation handling to use LocalNavigator and update screen structure * feat: Revamp bottom bar navigation and update screen structure * feat: Integrate LocalNavigator into various screens and update UI components * feat: Add selected tags filter chips to Home screen * feat: Enhance Home screen with LocalSharedText and update navigation handling
1 parent fbfaaf9 commit 5788818

File tree

16 files changed

+1138
-609
lines changed

16 files changed

+1138
-609
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[*.{kt,kts}]
22
ktlint_function_naming_ignore_when_annotated_with = Composable
3+
compose_allowed_composition_locals = LocalNavigator,LocalSharedText

app/src/main/java/com/yogeshpaliyal/deepr/DeeprApplication.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import kotlinx.serialization.json.Json
3333
import org.koin.android.ext.koin.androidContext
3434
import org.koin.core.context.startKoin
3535
import org.koin.core.module.dsl.viewModel
36+
import org.koin.core.module.dsl.viewModelOf
3637
import org.koin.dsl.module
3738

3839
class DeeprApplication : Application() {
@@ -90,7 +91,7 @@ class DeeprApplication : Application() {
9091
}
9192
}
9293

93-
viewModel { AccountViewModel(get(), get(), get(), get(), get(), get(), get()) }
94+
viewModelOf(::AccountViewModel)
9495

9596
single {
9697
HtmlParser()

app/src/main/java/com/yogeshpaliyal/deepr/MainActivity.kt

Lines changed: 108 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,43 @@ import android.os.Bundle
66
import androidx.activity.ComponentActivity
77
import androidx.activity.compose.setContent
88
import androidx.activity.enableEdgeToEdge
9-
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.animation.AnimatedVisibility
10+
import androidx.compose.animation.slideInVertically
11+
import androidx.compose.animation.slideOutVertically
12+
import androidx.compose.foundation.layout.WindowInsets
13+
import androidx.compose.material3.BottomAppBar
14+
import androidx.compose.material3.BottomAppBarDefaults
1015
import androidx.compose.material3.ExperimentalMaterial3Api
16+
import androidx.compose.material3.Icon
17+
import androidx.compose.material3.NavigationBarItem
18+
import androidx.compose.material3.Scaffold
1119
import androidx.compose.material3.Surface
1220
import androidx.compose.material3.Text
1321
import androidx.compose.runtime.Composable
22+
import androidx.compose.runtime.CompositionLocalProvider
23+
import androidx.compose.runtime.compositionLocalOf
1424
import androidx.compose.runtime.getValue
15-
import androidx.compose.runtime.mutableStateListOf
1625
import androidx.compose.runtime.remember
1726
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
28+
import androidx.compose.ui.platform.LocalHapticFeedback
29+
import androidx.compose.ui.platform.LocalLayoutDirection
30+
import androidx.compose.ui.res.stringResource
1831
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1932
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
2033
import androidx.navigation3.runtime.NavEntry
2134
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
2235
import androidx.navigation3.ui.NavDisplay
2336
import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
2437
import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore
25-
import com.yogeshpaliyal.deepr.ui.screens.AboutUs
26-
import com.yogeshpaliyal.deepr.ui.screens.AboutUsScreen
27-
import com.yogeshpaliyal.deepr.ui.screens.BackupScreen
28-
import com.yogeshpaliyal.deepr.ui.screens.BackupScreenContent
29-
import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServer
30-
import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServerScreen
31-
import com.yogeshpaliyal.deepr.ui.screens.RestoreScreen
32-
import com.yogeshpaliyal.deepr.ui.screens.RestoreScreenContent
38+
import com.yogeshpaliyal.deepr.ui.BaseScreen
39+
import com.yogeshpaliyal.deepr.ui.LocalNavigator
40+
import com.yogeshpaliyal.deepr.ui.Screen
41+
import com.yogeshpaliyal.deepr.ui.TopLevelBackStack
42+
import com.yogeshpaliyal.deepr.ui.TopLevelRoute
3343
import com.yogeshpaliyal.deepr.ui.screens.Settings
34-
import com.yogeshpaliyal.deepr.ui.screens.SettingsScreen
35-
import com.yogeshpaliyal.deepr.ui.screens.TransferLinkLocalNetworkServer
36-
import com.yogeshpaliyal.deepr.ui.screens.TransferLinkLocalServerScreen
37-
import com.yogeshpaliyal.deepr.ui.screens.home.Home
38-
import com.yogeshpaliyal.deepr.ui.screens.home.HomeScreen
44+
import com.yogeshpaliyal.deepr.ui.screens.home.Dashboard2
45+
import com.yogeshpaliyal.deepr.ui.screens.home.TagSelectionScreen
3946
import com.yogeshpaliyal.deepr.ui.theme.DeeprTheme
4047
import com.yogeshpaliyal.deepr.util.LanguageUtil
4148
import kotlinx.coroutines.flow.MutableStateFlow
@@ -81,7 +88,9 @@ class MainActivity : ComponentActivity() {
8188

8289
setContent {
8390
val preferenceDataStore = remember { AppPreferenceDataStore(this) }
84-
val themeMode by preferenceDataStore.getThemeMode.collectAsStateWithLifecycle(initialValue = "system")
91+
val themeMode by preferenceDataStore.getThemeMode.collectAsStateWithLifecycle(
92+
initialValue = "system",
93+
)
8594

8695
DeeprTheme(themeMode = themeMode) {
8796
Surface {
@@ -123,70 +132,96 @@ class MainActivity : ComponentActivity() {
123132
}
124133
}
125134

135+
private val TOP_LEVEL_ROUTES: List<TopLevelRoute> =
136+
listOf(Dashboard2(), TagSelectionScreen, Settings)
137+
138+
val LocalSharedText =
139+
compositionLocalOf<Pair<SharedLink?, () -> Unit>?> { null }
140+
141+
@OptIn(ExperimentalMaterial3Api::class)
126142
@Composable
127143
fun Dashboard(
128144
modifier: Modifier = Modifier,
129145
sharedText: SharedLink? = null,
130146
resetSharedText: () -> Unit,
131147
) {
132-
val backStack = remember(sharedText) { mutableStateListOf<Any>(Home) }
133-
134-
Column(modifier = modifier) {
135-
NavDisplay(
136-
backStack = backStack,
137-
entryDecorators =
138-
listOf(
139-
// Add the default decorators for managing scenes and saving state
140-
rememberSceneSetupNavEntryDecorator(),
141-
rememberSavedStateNavEntryDecorator(),
142-
// Then add the view model store decorator
143-
rememberViewModelStoreNavEntryDecorator(),
144-
),
145-
onBack = { backStack.removeLastOrNull() },
146-
entryProvider = { key ->
147-
when (key) {
148-
is Home ->
149-
NavEntry(key) {
150-
HomeScreen(
151-
backStack,
152-
sharedText = sharedText,
153-
resetSharedText = resetSharedText,
154-
)
155-
}
156-
157-
is Settings ->
158-
NavEntry(key) {
159-
SettingsScreen(backStack)
160-
}
161-
162-
is AboutUs ->
163-
NavEntry(key) {
164-
AboutUsScreen(backStack)
165-
}
166-
167-
is LocalNetworkServer ->
168-
NavEntry(key) {
169-
LocalNetworkServerScreen(backStack)
170-
}
171-
172-
is TransferLinkLocalNetworkServer ->
173-
NavEntry(key) {
174-
TransferLinkLocalServerScreen(backStack)
175-
}
176-
177-
is BackupScreen ->
178-
NavEntry(key) {
179-
BackupScreenContent(backStack)
148+
val backStack =
149+
remember {
150+
TopLevelBackStack<BaseScreen>(
151+
Dashboard2(),
152+
)
153+
}
154+
val current = backStack.getLast()
155+
val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
156+
val hapticFeedback = LocalHapticFeedback.current
157+
val layoutDirection = LocalLayoutDirection.current
158+
159+
CompositionLocalProvider(LocalSharedText provides Pair(sharedText, resetSharedText)) {
160+
CompositionLocalProvider(LocalNavigator provides backStack) {
161+
Scaffold(
162+
modifier = modifier,
163+
bottomBar = {
164+
AnimatedVisibility(
165+
(TOP_LEVEL_ROUTES.any { it::class == current::class }),
166+
enter = slideInVertically(initialOffsetY = { it }),
167+
exit = slideOutVertically(targetOffsetY = { it }),
168+
) {
169+
BottomAppBar(scrollBehavior = scrollBehavior) {
170+
TOP_LEVEL_ROUTES.forEach { topLevelRoute ->
171+
val isSelected =
172+
topLevelRoute::class == backStack.topLevelKey::class
173+
NavigationBarItem(
174+
selected = isSelected,
175+
onClick = {
176+
hapticFeedback.performHapticFeedback(HapticFeedbackType.ContextClick)
177+
backStack.addTopLevel(topLevelRoute)
178+
},
179+
label = {
180+
Text(stringResource(topLevelRoute.label))
181+
},
182+
icon = {
183+
Icon(
184+
imageVector = topLevelRoute.icon,
185+
contentDescription = null,
186+
)
187+
},
188+
)
189+
}
180190
}
181-
182-
is RestoreScreen ->
183-
NavEntry(key) {
184-
RestoreScreenContent(backStack)
191+
}
192+
},
193+
) { contentPadding ->
194+
NavDisplay(
195+
backStack = backStack.backStack,
196+
entryDecorators =
197+
listOf(
198+
// Add the default decorators for managing scenes and saving state
199+
rememberSceneSetupNavEntryDecorator(),
200+
rememberSavedStateNavEntryDecorator(),
201+
// Then add the view model store decorator
202+
rememberViewModelStoreNavEntryDecorator(),
203+
),
204+
onBack = {
205+
backStack.removeLast()
206+
},
207+
entryProvider = {
208+
NavEntry(it) { entryItem ->
209+
if (entryItem is TopLevelRoute) {
210+
entryItem.Content(
211+
WindowInsets(
212+
left = contentPadding.calculateLeftPadding(layoutDirection),
213+
right = contentPadding.calculateRightPadding(layoutDirection),
214+
top = contentPadding.calculateTopPadding(),
215+
bottom = contentPadding.calculateBottomPadding(),
216+
),
217+
)
218+
} else if (entryItem is Screen) {
219+
entryItem.Content()
220+
}
185221
}
186-
187-
else -> NavEntry(Unit) { Text("Unknown route") }
188-
}
189-
},
190-
)
222+
},
223+
)
224+
}
225+
}
191226
}
192227
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.yogeshpaliyal.deepr.ui
2+
3+
import androidx.compose.foundation.layout.WindowInsets
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.compositionLocalOf
6+
import androidx.compose.runtime.getValue
7+
import androidx.compose.runtime.mutableStateListOf
8+
import androidx.compose.runtime.mutableStateOf
9+
import androidx.compose.runtime.setValue
10+
import androidx.compose.runtime.snapshots.SnapshotStateList
11+
import androidx.compose.ui.graphics.vector.ImageVector
12+
import androidx.navigation3.runtime.NavKey
13+
import com.yogeshpaliyal.deepr.ui.screens.home.Dashboard2
14+
import kotlin.collections.remove
15+
16+
val LocalNavigator =
17+
compositionLocalOf<TopLevelBackStack<BaseScreen>> { TopLevelBackStack(Dashboard2()) }
18+
19+
sealed interface BaseScreen : NavKey
20+
21+
interface Screen : BaseScreen {
22+
@Composable
23+
fun Content()
24+
}
25+
26+
interface TopLevelRoute : BaseScreen {
27+
val icon: ImageVector
28+
29+
val label: Int
30+
31+
@Composable
32+
fun Content(windowInsets: WindowInsets)
33+
}
34+
35+
class TopLevelBackStack<T : Any>(
36+
startKey: T,
37+
) {
38+
// Maintain a stack for each top level route
39+
private var topLevelStacks: LinkedHashMap<T, SnapshotStateList<T>> =
40+
linkedMapOf(
41+
startKey to mutableStateListOf(startKey),
42+
)
43+
44+
// Expose the current top level route for consumers
45+
var topLevelKey by mutableStateOf(startKey)
46+
private set
47+
48+
// Expose the back stack so it can be rendered by the NavDisplay
49+
val backStack = mutableStateListOf(startKey)
50+
51+
private fun updateBackStack() =
52+
backStack.apply {
53+
clear()
54+
addAll(topLevelStacks.flatMap { it.value })
55+
}
56+
57+
fun clearStackAndAdd(key: T) {
58+
topLevelStacks.clear()
59+
addTopLevel(key)
60+
}
61+
62+
fun addTopLevel(key: T) {
63+
// If the top level doesn't exist, add it
64+
if (topLevelStacks[key] == null) {
65+
topLevelStacks.put(key, mutableStateListOf(key))
66+
} else {
67+
// Otherwise just move it to the end of the stacks
68+
topLevelStacks.apply {
69+
remove(key)?.let {
70+
put(key, it)
71+
}
72+
}
73+
}
74+
topLevelKey = key
75+
updateBackStack()
76+
}
77+
78+
fun add(key: T) {
79+
topLevelStacks[topLevelKey]?.add(key)
80+
updateBackStack()
81+
}
82+
83+
fun getLast() = backStack.last()
84+
85+
fun removeLast() {
86+
val removedKey = topLevelStacks[topLevelKey]?.removeLastOrNull()
87+
// If the removed key was a top level key, remove the associated top level stack
88+
topLevelStacks.remove(removedKey)
89+
topLevelKey = topLevelStacks.keys.last()
90+
updateBackStack()
91+
}
92+
}

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/AboutUs.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import androidx.compose.material3.Scaffold
2424
import androidx.compose.material3.Text
2525
import androidx.compose.material3.TopAppBar
2626
import androidx.compose.runtime.Composable
27-
import androidx.compose.runtime.snapshots.SnapshotStateList
2827
import androidx.compose.ui.Alignment
2928
import androidx.compose.ui.Modifier
3029
import androidx.compose.ui.draw.clip
@@ -38,20 +37,25 @@ import androidx.compose.ui.unit.LayoutDirection
3837
import androidx.compose.ui.unit.dp
3938
import com.yogeshpaliyal.deepr.BuildConfig
4039
import com.yogeshpaliyal.deepr.R
40+
import com.yogeshpaliyal.deepr.ui.LocalNavigator
41+
import com.yogeshpaliyal.deepr.ui.Screen
4142
import compose.icons.TablerIcons
4243
import compose.icons.tablericons.ArrowLeft
4344
import compose.icons.tablericons.BrandGithub
4445
import compose.icons.tablericons.BrandLinkedin
4546
import compose.icons.tablericons.BrandTwitter
4647

47-
data object AboutUs
48+
object AboutUs : Screen {
49+
@Composable
50+
override fun Content() {
51+
AboutUsScreen()
52+
}
53+
}
4854

4955
@OptIn(ExperimentalMaterial3Api::class)
5056
@Composable
51-
fun AboutUsScreen(
52-
backStack: SnapshotStateList<Any>,
53-
modifier: Modifier = Modifier,
54-
) {
57+
fun AboutUsScreen(modifier: Modifier = Modifier) {
58+
val backStack = LocalNavigator.current
5559
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
5660

5761
Scaffold(modifier = modifier.fillMaxSize(), topBar = {
@@ -62,7 +66,7 @@ fun AboutUsScreen(
6266
},
6367
navigationIcon = {
6468
IconButton(onClick = {
65-
backStack.removeLastOrNull()
69+
backStack.removeLast()
6670
}) {
6771
Icon(
6872
TablerIcons.ArrowLeft,

0 commit comments

Comments
 (0)