Skip to content
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

Navigation support #384

Merged
merged 4 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
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
Expand Up @@ -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

Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand All @@ -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
},
Expand All @@ -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()
}
}
)
Expand Down
Loading
Loading