Skip to content

Commit

Permalink
Merge pull request #384 from SuhasDissa/navigation-support
Browse files Browse the repository at this point in the history
Navigation support
SuhasDissa authored Apr 2, 2024
2 parents 901c572 + f85eed7 commit 6882bf9
Showing 17 changed files with 574 additions and 478 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -94,6 +94,7 @@ dependencies {

// Room database
implementation("androidx.room:room-ktx:2.5.1")
implementation("androidx.navigation:navigation-compose:2.5.2")
kapt("androidx.room:room-compiler:2.5.1")

// Testing
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -46,7 +46,8 @@

<activity
android:name=".ui.activities.MainActivity"
android:exported="true">
android:exported="true"
android:windowSoftInputMode="adjustResize">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
125 changes: 125 additions & 0 deletions app/src/main/java/com/bnyro/contacts/nav/NavContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.bnyro.contacts.nav

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.navigation.compose.rememberNavController
import com.bnyro.contacts.ui.activities.MainActivity

val bottomNavItems = listOf(
NavRoutes.Contacts,
NavRoutes.Messages
)

@Composable
fun NavContainer(
initialTabIndex: Int
) {
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 && (selectedRoute == NavRoutes.Contacts || selectedRoute == NavRoutes.Messages)) {
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()
)
}
}
}
88 changes: 88 additions & 0 deletions app/src/main/java/com/bnyro/contacts/nav/NavHost.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.bnyro.contacts.nav

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.bnyro.contacts.ui.models.ContactsModel
import com.bnyro.contacts.ui.models.SmsModel
import com.bnyro.contacts.ui.models.ThemeModel
import com.bnyro.contacts.ui.screens.AboutScreen
import com.bnyro.contacts.ui.screens.ContactsPage
import com.bnyro.contacts.ui.screens.SettingsScreen
import com.bnyro.contacts.ui.screens.SmsListScreen
import com.bnyro.contacts.ui.screens.SmsThreadScreen

@Composable
fun AppNavHost(
navController: NavHostController,
startDestination: NavRoutes,
modifier: Modifier = Modifier
) {
val smsModel: SmsModel = viewModel(factory = SmsModel.Factory)
val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory)
val themeModel: ThemeModel = viewModel()

val viewModelStoreOwner: ViewModelStoreOwner = LocalViewModelStoreOwner.current!!

NavHost(navController, startDestination = startDestination.route, modifier = modifier) {
composable(NavRoutes.About.route) {
AboutScreen {
navController.popBackStack()
}
}
composable(NavRoutes.Settings.route) {
SettingsScreen(themeModel = themeModel, smsModel = smsModel) {
navController.popBackStack()
}
}
composable(NavRoutes.Contacts.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
ContactsPage(null,
onNavigate = {
navController.navigate(it.route)
})
}
}
composable(NavRoutes.Messages.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
SmsListScreen(
smsModel = smsModel,
contactsModel = contactsModel,
scrollConnection = null,
onNavigate = {
navController.navigate(it.route)
},
onClickMessage = { address, contactData ->
smsModel.currentContactData = contactData
navController.navigate("${NavRoutes.MessageThread.route}/$address")
}
)
}
}
composable(
"${NavRoutes.MessageThread.route}/{address}",
listOf(navArgument("address") { type = NavType.StringType })
) {
val address = it.arguments?.getString("address")
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
SmsThreadScreen(
smsModel = smsModel,
contactsModel = contactsModel,
contactsData = remember { smsModel.currentContactData },
address = address.orEmpty()
) {
navController.popBackStack()
}
}
}
}
}
28 changes: 28 additions & 0 deletions app/src/main/java/com/bnyro/contacts/nav/NavRoutes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.bnyro.contacts.nav

import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Message
import androidx.compose.material.icons.rounded.Person
import androidx.compose.ui.graphics.vector.ImageVector
import com.bnyro.contacts.R

sealed class NavRoutes(
val route: String,
@StringRes val stringRes: Int? = null,
val icon: ImageVector? = null
) {
object About : NavRoutes("about", null, null)
object Settings : NavRoutes("settings", null, null)
object Contacts : NavRoutes("contacts", R.string.contacts, Icons.Rounded.Person)
object Messages : NavRoutes("messages", R.string.messages, Icons.Rounded.Message)
object MessageThread : NavRoutes("message_thread", null, null)
}

val allRoutes = listOf(
NavRoutes.About,
NavRoutes.Settings,
NavRoutes.Contacts,
NavRoutes.Messages,
NavRoutes.MessageThread,
)
Original file line number Diff line number Diff line change
@@ -3,19 +3,19 @@ package com.bnyro.contacts.ui.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.provider.ContactsContract.Intents
import android.provider.ContactsContract.QuickContact
import androidx.activity.compose.setContent
import com.bnyro.contacts.ext.parcelable
import com.bnyro.contacts.nav.NavContainer
import com.bnyro.contacts.obj.ContactData
import com.bnyro.contacts.obj.ValueWithType
import com.bnyro.contacts.ui.components.ConfirmImportContactsDialog
import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog
import com.bnyro.contacts.ui.screens.MainAppContent
import com.bnyro.contacts.ui.theme.ConnectYouTheme
import com.bnyro.contacts.util.BackupHelper
import com.bnyro.contacts.util.ContactsHelper
import com.bnyro.contacts.util.Preferences
import com.bnyro.contacts.util.IntentHelper
import java.net.URLDecoder

@@ -34,9 +34,11 @@ class MainActivity : BaseActivity() {

smsModel.initialAddressAndBody = getInitialSmsAddressAndBody()

val initialTabIndex = smsModel.initialAddressAndBody?.let { 1 }
?: Preferences.getInt(Preferences.homeTabKey, 0)
setContent {
ConnectYouTheme(themeModel.themeMode) {
MainAppContent(smsModel)
NavContainer(initialTabIndex)
getInsertOrEditNumber()?.let {
AddToContactDialog(it)
}
Original file line number Diff line number Diff line change
@@ -21,22 +21,22 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.bnyro.contacts.R
import com.bnyro.contacts.enums.SortOrder
import com.bnyro.contacts.obj.ContactData
import com.bnyro.contacts.ui.components.base.ClickableIcon
import com.bnyro.contacts.ui.components.dialogs.DialogButton
import com.bnyro.contacts.ui.models.ContactsModel

@Composable
fun NumberPickerDialog(
contactsModel: ContactsModel,
onDismissRequest: () -> Unit,
onNumberSelect: (number: String) -> Unit
onNumberSelect: (number: String, contactData: ContactData?) -> Unit,
) {
val contactsModel: ContactsModel = viewModel()

var numbersToSelectFrom by remember {
mutableStateOf(listOf<String>())
var numbersToSelectFrom: Pair<ContactData?, List<String>> by remember {
mutableStateOf(null to listOf())
}

AlertDialog(
@@ -71,7 +71,7 @@ fun NumberPickerDialog(
modifier = Modifier.padding(top = 3.dp),
icon = Icons.Default.Send
) {
onNumberSelect.invoke(numberInput)
onNumberSelect.invoke(numberInput, null)
}
}
Spacer(modifier = Modifier.height(10.dp))
@@ -83,11 +83,11 @@ fun NumberPickerDialog(
selected = false,
onSinglePress = {
if (it.numbers.size > 1) {
numbersToSelectFrom = it.numbers.map { num -> num.value }
numbersToSelectFrom = it to it.numbers.map { num -> num.value }
return@ContactItem true
}

onNumberSelect.invoke(it.numbers.first().value)
onNumberSelect.invoke(it.numbers.first().value, it)
onDismissRequest.invoke()
true
},
@@ -98,23 +98,23 @@ fun NumberPickerDialog(
}
)

if (numbersToSelectFrom.isNotEmpty()) {
if (numbersToSelectFrom.second.isNotEmpty()) {
AlertDialog(
onDismissRequest = { numbersToSelectFrom = emptyList() },
onDismissRequest = { numbersToSelectFrom = null to emptyList() },
text = {
LazyColumn {
items(numbersToSelectFrom) {
items(numbersToSelectFrom.second) {
ClickableText(text = it) {
onNumberSelect.invoke(it)
numbersToSelectFrom = emptyList()
onNumberSelect.invoke(it, numbersToSelectFrom.first)
numbersToSelectFrom = null to emptyList()
onDismissRequest.invoke()
}
}
}
},
confirmButton = {
DialogButton(stringResource(R.string.cancel)) {
numbersToSelectFrom = emptyList()
numbersToSelectFrom = null to emptyList()
}
}
)
Loading

0 comments on commit 6882bf9

Please sign in to comment.