@@ -10,11 +10,11 @@ import androidx.activity.viewModels
1010import androidx.compose.foundation.background
1111import androidx.compose.foundation.layout.Arrangement
1212import androidx.compose.foundation.layout.Box
13- import androidx.compose.foundation.layout.WindowInsets
1413import androidx.compose.foundation.layout.fillMaxSize
1514import androidx.compose.foundation.layout.fillMaxWidth
1615import androidx.compose.foundation.layout.height
1716import androidx.compose.foundation.layout.padding
17+ import androidx.compose.foundation.layout.systemBarsPadding
1818import androidx.compose.foundation.lazy.LazyColumn
1919import androidx.compose.foundation.lazy.rememberLazyListState
2020import androidx.compose.material3.Card
@@ -30,18 +30,27 @@ import androidx.compose.ui.graphics.Color
3030import androidx.compose.ui.tooling.preview.Preview
3131import androidx.compose.ui.unit.dp
3232import androidx.lifecycle.compose.collectAsStateWithLifecycle
33+ import androidx.navigation.NavGraph.Companion.findStartDestination
34+ import androidx.navigation.NavHostController
3335import androidx.navigation.compose.NavHost
3436import androidx.navigation.compose.composable
37+ import androidx.navigation.compose.currentBackStackEntryAsState
3538import androidx.navigation.compose.rememberNavController
3639import com.kickstarter.features.home.data.Tab
3740import com.kickstarter.features.home.ui.components.FloatingBottomNav
3841import com.kickstarter.features.home.viewmodel.HomeScreenViewModel
42+ import com.kickstarter.features.search.viewmodel.FilterMenuViewModel
43+ import com.kickstarter.features.search.viewmodel.SearchAndFilterViewModel
3944import com.kickstarter.libs.Environment
45+ import com.kickstarter.libs.utils.ThirdPartyEventValues
4046import com.kickstarter.libs.utils.TransitionUtils
4147import com.kickstarter.libs.utils.extensions.getEnvironment
4248import com.kickstarter.libs.utils.extensions.isDarkModeEnabled
49+ import com.kickstarter.ui.activities.compose.search.SearchAndFilterScreen
4350import com.kickstarter.ui.compose.designsystem.KickstarterApp
4451import com.kickstarter.ui.extensions.setUpConnectivityStatusCheck
52+ import com.kickstarter.ui.extensions.startPreLaunchProjectActivity
53+ import com.kickstarter.ui.extensions.startProjectActivity
4554import com.kickstarter.ui.extensions.transition
4655import kotlin.getValue
4756import kotlin.random.Random
@@ -52,6 +61,12 @@ class HomeActivity : ComponentActivity() {
5261 private lateinit var viewModelFactory: HomeScreenViewModel .Factory
5362 private val viewModel: HomeScreenViewModel by viewModels { viewModelFactory }
5463
64+ // Search related VM's
65+ private lateinit var searchVMFactory: SearchAndFilterViewModel .Factory
66+ private lateinit var filterMenuViewModelFactory: FilterMenuViewModel .Factory
67+ private val searchVM: SearchAndFilterViewModel by viewModels { searchVMFactory }
68+ private val filterMenuVM: FilterMenuViewModel by viewModels { filterMenuViewModelFactory }
69+
5570 override fun onCreate (savedInstanceState : Bundle ? ) {
5671 super .onCreate(savedInstanceState)
5772 setUpConnectivityStatusCheck(lifecycle)
@@ -60,6 +75,9 @@ class HomeActivity : ComponentActivity() {
6075 this .getEnvironment()?.let { env ->
6176 environment = env
6277 viewModelFactory = HomeScreenViewModel .Factory (env)
78+ searchVMFactory = SearchAndFilterViewModel .Factory (environment)
79+ filterMenuViewModelFactory = FilterMenuViewModel .Factory (environment)
80+ filterMenuVM.getRootCategories()
6381 }
6482
6583 setContent {
@@ -70,12 +88,12 @@ class HomeActivity : ComponentActivity() {
7088 listOf (
7189 Tab .Home ,
7290 Tab .Search ,
73- if (homeUIState.userAvatarUrl.isNotEmpty() ) Tab .Profile (homeUIState.userAvatarUrl) else Tab .LogIn
91+ if (homeUIState.isLoggedInUser ) Tab .Profile (homeUIState.userAvatarUrl) else Tab .LogIn
7492 )
7593 }
7694
7795 KickstarterApp (useDarkTheme = darModeEnabled) {
78- App (tabs = tabs )
96+ App (tabs)
7997 }
8098 }
8199
@@ -86,52 +104,105 @@ class HomeActivity : ComponentActivity() {
86104 }
87105 })
88106 }
89- }
90107
91- @Composable
92- @Preview(name = " Light" , uiMode = Configuration .UI_MODE_NIGHT_NO )
93- @Preview(name = " Dark" , uiMode = Configuration .UI_MODE_NIGHT_YES )
94- fun HomeActivityPreview () {
95- val tabs = listOf (
96- Tab .Home ,
97- Tab .Search ,
98- Tab .LogIn
99- )
100- KickstarterApp {
101- App (tabs = tabs)
108+ @Composable
109+ @Preview(name = " Light" , uiMode = Configuration .UI_MODE_NIGHT_NO )
110+ @Preview(name = " Dark" , uiMode = Configuration .UI_MODE_NIGHT_YES )
111+ fun HomeActivityPreview () {
112+ val tabs = listOf (
113+ Tab .Home ,
114+ Tab .Search ,
115+ Tab .LogIn
116+ )
117+ KickstarterApp {
118+ App (tabs = tabs)
119+ }
120+ }
121+
122+ @Composable
123+ private fun App (tabs : List <Tab >) {
124+ val navController = rememberNavController()
125+ val shouldShowBottomNav = remember { mutableStateOf(true ) }
126+ val backStack by navController.currentBackStackEntryAsState()
127+ val currentRoute = backStack?.destination?.route
128+
129+ val activeTab = tabs.find { it.route == currentRoute } ? : tabs.first()
130+ Scaffold (
131+ modifier = Modifier .systemBarsPadding(),
132+ bottomBar = {
133+ if (shouldShowBottomNav.value) {
134+ FloatingBottomNav (
135+ tabs = tabs,
136+ activeTab = activeTab,
137+ onTabClicked = { tab ->
138+ navController.navWithDefaults(tab.route)
139+ }
140+ )
141+ }
142+ }
143+ ) { inner ->
144+ NavHost (
145+ navController = navController,
146+ startDestination = tabs.first().route,
147+ modifier = Modifier
148+ .fillMaxSize()
149+ .padding(top = inner.calculateTopPadding())
150+ ) {
151+ tabs.map { tab ->
152+ when (tab) {
153+ is Tab .Search -> {
154+ composable(tab.route) {
155+ SearchAndFilterScreen (
156+ env = environment,
157+ searchViewModel = searchVM,
158+ filterMenuVM = filterMenuVM,
159+ onBackClicked = { },
160+ preLaunchedCallback = { project, tag ->
161+ startPreLaunchProjectActivity(
162+ project = project,
163+ previousScreen = ThirdPartyEventValues .ScreenName .SEARCH .value,
164+ refTag = tag
165+ )
166+ },
167+ projectCallback = { projectAndRef ->
168+ startProjectActivity(
169+ project = projectAndRef.first,
170+ refTag = projectAndRef.second,
171+ previousScreen = ThirdPartyEventValues .ScreenName .SEARCH .value
172+ )
173+ },
174+ )
175+ }
176+ }
177+
178+ else -> {
179+ composable(tab.route) {
180+ ScreenStub (tab.route)
181+ }
182+ }
183+ }
184+ }
185+ }
186+ }
102187 }
103188}
104189
105190/* *
106- * Home Screen composable parent UI
191+ * Navigates to a specified route using the standard default configuration for bottom navigation.
107192 *
108- * @param tabs: Contains the list of tabs represented on the floating bottomNav
193+ * This helper ensures that:
194+ * 1. The back stack is popped up to the start destination to avoid a large stack of screens.
195+ * 2. State is saved and restored when switching between tabs.
196+ * 3. Only a single instance of a destination is launched (launchSingleTop) to prevent multiple
197+ * copies of the same screen when re-selecting a tab.
198+ *
199+ * @param route The destination route to navigate to.
109200 */
110- @Composable
111- fun App (
112- tabs : List <Tab > = listOf(Tab .Home , Tab .Search , Tab .LogIn )
113- ) {
114- val nav = rememberNavController()
115- val shouldShowBottomNav = remember { mutableStateOf(true ) }
116- Scaffold (
117- contentWindowInsets = WindowInsets (0 , 0 , 0 , 0 ),
118- bottomBar = {
119- if (shouldShowBottomNav.value) {
120- FloatingBottomNav (nav, tabs = tabs)
121- }
122- }
123- ) { inner ->
124- NavHost (
125- navController = nav,
126- startDestination = Tab .Home .route,
127- modifier = Modifier
128- .fillMaxSize()
129- .padding(top = inner.calculateTopPadding())
130- ) {
131- tabs.map { tab ->
132- composable(tab.route) { ScreenStub (tab.route) }
133- }
134- }
201+ private fun NavHostController.navWithDefaults (route : String ) {
202+ this .navigate(route) {
203+ popUpTo(this @navWithDefaults.graph.findStartDestination().id) { saveState = true }
204+ launchSingleTop = true
205+ restoreState = true
135206 }
136207}
137208
0 commit comments