Skip to content

Commit

Permalink
feat: use nested navigation for home tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
SuhasDissa committed Apr 27, 2024
1 parent 72ec0db commit 66edda8
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 200 deletions.
116 changes: 116 additions & 0 deletions app/src/main/java/com/bnyro/contacts/navigation/HomeNavContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.bnyro.contacts.navigation

import android.content.res.Configuration
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.bnyro.contacts.presentation.screens.contacts.model.ContactsModel
import com.bnyro.contacts.presentation.screens.dialer.model.DialerModel
import com.bnyro.contacts.presentation.screens.settings.model.ThemeModel
import com.bnyro.contacts.presentation.screens.sms.model.SmsModel

@Composable
fun HomeNavContainer(
initialTab: HomeRoutes,
onNavigate: (String) -> Unit,
smsModel: SmsModel,
contactsModel: ContactsModel,
dialerModel: DialerModel,
themeModel: ThemeModel
) {
val navController = rememberNavController()

var selectedRoute by remember {
mutableStateOf(initialTab)
}

// listen for destination changes (e.g. back presses)
DisposableEffect(Unit) {
val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
HomeRoutes.all.firstOrNull { it.route == destination.route }
?.also { selectedRoute = it }
}
navController.addOnDestinationChangedListener(listener)

onDispose {
navController.removeOnDestinationChangedListener(listener)
}
}

val orientation = LocalConfiguration.current.orientation
Scaffold(
bottomBar = {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
NavigationBar(
tonalElevation = 5.dp
) {
HomeRoutes.all.forEach {
NavigationBarItem(
label = {
Text(stringResource(it.stringRes))
},
icon = {
Icon(it.icon, null)
},
selected = it == selectedRoute,
onClick = {
navController.popBackStack()
navController.navigate(it.route)
}
)
}
}
}
}
) { pV ->
Row(
Modifier
.fillMaxSize()
.padding(pV)
) {
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
NavigationRail {
HomeRoutes.all.forEach {
NavigationRailItem(selected = it == selectedRoute,
onClick = {
navController.popBackStack()
navController.navigate(it.route)
},
icon = { Icon(it.icon, null) },
label = {
Text(stringResource(it.stringRes))
})
}
}
}
HomeNavHost(
navController = navController,
startTab = initialTab,
onNavigate = onNavigate,
smsModel = smsModel,
contactsModel = contactsModel,
dialerModel = dialerModel,
themeModel = themeModel
)
}
}
}
63 changes: 63 additions & 0 deletions app/src/main/java/com/bnyro/contacts/navigation/HomeNavHost.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.bnyro.contacts.navigation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.bnyro.contacts.presentation.screens.calllog.CallLogsScreen
import com.bnyro.contacts.presentation.screens.contacts.ContactsPage
import com.bnyro.contacts.presentation.screens.contacts.model.ContactsModel
import com.bnyro.contacts.presentation.screens.dialer.model.DialerModel
import com.bnyro.contacts.presentation.screens.settings.model.ThemeModel
import com.bnyro.contacts.presentation.screens.sms.SmsListScreen
import com.bnyro.contacts.presentation.screens.sms.model.SmsModel

@Composable
fun HomeNavHost(
navController: NavHostController,
onNavigate: (String) -> Unit,
startTab: HomeRoutes,
modifier: Modifier = Modifier,
smsModel: SmsModel,
contactsModel: ContactsModel,
dialerModel: DialerModel,
themeModel: ThemeModel
) {
val viewModelStoreOwner: ViewModelStoreOwner = LocalViewModelStoreOwner.current!!

NavHost(navController, startDestination = startTab.route, modifier = modifier) {
composable(HomeRoutes.Contacts.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
ContactsPage(null,
onNavigate = {
onNavigate.invoke(it.route)
})
}
}
composable(HomeRoutes.Phone.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
CallLogsScreen(contactsModel, dialerModel, themeModel)
}
}
composable(HomeRoutes.Messages.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
SmsListScreen(
smsModel = smsModel,
contactsModel = contactsModel,
scrollConnection = null,
onNavigate = {
onNavigate.invoke(it.route)
},
onClickMessage = { address, contactData ->
smsModel.currentContactData = contactData
onNavigate.invoke("${NavRoutes.MessageThread.route}/$address")
}
)
}
}
}
}
137 changes: 20 additions & 117 deletions app/src/main/java/com/bnyro/contacts/navigation/NavContainer.kt
Original file line number Diff line number Diff line change
@@ -1,129 +1,32 @@
package com.bnyro.contacts.navigation

import android.content.res.Configuration
import androidx.activity.addCallback
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import com.bnyro.contacts.ui.activities.MainActivity

val bottomNavItems = listOf(
NavRoutes.Phone,
NavRoutes.Contacts,
NavRoutes.Messages
)
import com.bnyro.contacts.presentation.screens.contacts.model.ContactsModel
import com.bnyro.contacts.presentation.screens.dialer.model.DialerModel
import com.bnyro.contacts.presentation.screens.settings.model.ThemeModel
import com.bnyro.contacts.presentation.screens.sms.model.SmsModel

@Composable
fun NavContainer(
initialTabIndex: Int
initialTab: HomeRoutes
) {
val context = LocalContext.current
val navController = rememberNavController()

val initialTab = bottomNavItems[initialTabIndex.coerceIn(0, 1)]
var selectedRoute by remember {
mutableStateOf(initialTab)
}
LaunchedEffect(Unit) {
val activity = context as MainActivity
activity.onBackPressedDispatcher.addCallback {
if (selectedRoute != NavRoutes.Settings && selectedRoute != NavRoutes.About) {
activity.finish()
} else {
navController.popBackStack()
}
}
}

// listen for destination changes (e.g. back presses)
DisposableEffect(Unit) {
val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
allRoutes.firstOrNull { it.route == destination.route?.split("/")?.first() }
?.let { selectedRoute = it }
}
navController.addOnDestinationChangedListener(listener)

onDispose {
navController.removeOnDestinationChangedListener(listener)
}
}

val orientation = LocalConfiguration.current.orientation
Scaffold(
bottomBar = {
if (orientation == Configuration.ORIENTATION_PORTRAIT && bottomNavItems.contains(
selectedRoute
)
) {
NavigationBar(
tonalElevation = 5.dp
) {
bottomNavItems.forEach {
NavigationBarItem(
label = {
Text(stringResource(it.stringRes!!))
},
icon = {
Icon(it.icon!!, null)
},
selected = it == selectedRoute,
onClick = {
selectedRoute = it
navController.navigate(it.route)
}
)
}
}
}
}
) { pV ->
Row(
Modifier
.fillMaxSize()
.padding(pV)
) {
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
NavigationRail {
bottomNavItems.forEach {
NavigationRailItem(selected = it == selectedRoute,
onClick = {
selectedRoute = it
navController.navigate(it.route)
},
icon = { Icon(it.icon!!, null) },
label = {
Text(stringResource(it.stringRes!!))
})
}
}
}
AppNavHost(
navController,
startDestination = initialTab,
modifier = Modifier
.fillMaxSize()
)
}
}
val smsModel: SmsModel = viewModel()
val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory)
val dialerModel: DialerModel = viewModel()
val themeModel: ThemeModel = viewModel()
AppNavHost(
navController,
initialTab = initialTab,
modifier = Modifier
.fillMaxSize(),
smsModel = smsModel,
contactsModel = contactsModel,
dialerModel = dialerModel,
themeModel = themeModel
)
}
Loading

0 comments on commit 66edda8

Please sign in to comment.