22
33package com.smarttoolfactory.tutorial3_1navigation
44
5+ import android.Manifest
56import android.annotation.SuppressLint
7+ import android.content.pm.PackageManager
8+ import android.net.Uri
9+ import android.os.Build
10+ import androidx.activity.compose.rememberLauncherForActivityResult
11+ import androidx.activity.result.contract.ActivityResultContracts
612import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
713import androidx.compose.animation.core.tween
814import androidx.compose.foundation.background
915import androidx.compose.foundation.clickable
1016import androidx.compose.foundation.layout.Arrangement
1117import androidx.compose.foundation.layout.Column
1218import androidx.compose.foundation.layout.PaddingValues
19+ import androidx.compose.foundation.layout.Row
1320import androidx.compose.foundation.layout.Spacer
1421import androidx.compose.foundation.layout.fillMaxSize
1522import androidx.compose.foundation.layout.fillMaxWidth
@@ -18,9 +25,13 @@ import androidx.compose.foundation.layout.padding
1825import androidx.compose.foundation.lazy.LazyColumn
1926import androidx.compose.foundation.lazy.items
2027import androidx.compose.foundation.shape.RoundedCornerShape
28+ import androidx.compose.material.icons.Icons
29+ import androidx.compose.material.icons.filled.Notifications
2130import androidx.compose.material3.Button
2231import androidx.compose.material3.ExperimentalMaterial3Api
2332import androidx.compose.material3.Icon
33+ import androidx.compose.material3.IconButton
34+ import androidx.compose.material3.MaterialTheme
2435import androidx.compose.material3.NavigationBar
2536import androidx.compose.material3.NavigationBarItem
2637import androidx.compose.material3.OutlinedTextField
@@ -39,10 +50,12 @@ import androidx.compose.ui.Alignment
3950import androidx.compose.ui.Modifier
4051import androidx.compose.ui.draw.shadow
4152import androidx.compose.ui.graphics.Color
53+ import androidx.compose.ui.platform.LocalContext
4254import androidx.compose.ui.text.font.FontWeight
4355import androidx.compose.ui.tooling.preview.Preview
4456import androidx.compose.ui.unit.dp
4557import androidx.compose.ui.unit.sp
58+ import androidx.core.content.ContextCompat
4659import androidx.hilt.navigation.compose.hiltViewModel
4760import androidx.lifecycle.ViewModel
4861import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -57,6 +70,7 @@ import androidx.navigation.compose.composable
5770import androidx.navigation.compose.currentBackStackEntryAsState
5871import androidx.navigation.compose.navigation
5972import androidx.navigation.compose.rememberNavController
73+ import androidx.navigation.navDeepLink
6074import androidx.navigation.toRoute
6175import dagger.hilt.android.lifecycle.HiltViewModel
6276import kotlinx.coroutines.delay
@@ -73,12 +87,52 @@ import kotlin.random.Random
7387@Preview
7488@Composable
7589fun Tutorial6_1Screen () {
90+
91+ // In this example RegisterViewModel is shared by
92+ // RegisterGraph and BottomNavigationRoute.DashboardRoute
93+ // with HomeGraph NavBackEntry
94+ // When user is not logged in RegisterScreen is shown by navigating from
95+ // BottomNavigationRoute.DashboardRoute
96+
97+ // Also if user enters app via deeplink from terminal
98+ // or Notification startDestination is changed from Splash to ProfileGraph
99+ // to have conditional startDestination too.
100+
101+
76102 val navController = rememberNavController()
77103
104+ val context = LocalContext .current
105+
106+ var hasNotificationPermission by remember {
107+ mutableStateOf(
108+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
109+
110+ ContextCompat .checkSelfPermission(
111+ context,
112+ Manifest .permission.POST_NOTIFICATIONS
113+ ) == PackageManager .PERMISSION_GRANTED
114+ } else true
115+ )
116+ }
117+
118+ val permissionRequest =
119+ rememberLauncherForActivityResult(contract = ActivityResultContracts .RequestPermission ()) { result ->
120+ hasNotificationPermission = result
121+
122+ if (hasNotificationPermission) {
123+ showNotification(context)
124+ }
125+ }
126+
127+ val deeplink: Uri ? = (LocalContext .current as ? MainActivity )?.intent?.data
128+ val isDeeplink = deeplink != null
129+
130+ println (" Deeplink: $deeplink " )
131+
78132 NavHost (
79133 modifier = Modifier .fillMaxSize(),
80134 navController = navController,
81- startDestination = Splash ,
135+ startDestination = if (isDeeplink) ProfileGraph else Splash ,
82136 enterTransition = {
83137 slideIntoContainer(
84138 towards = SlideDirection .Start ,
@@ -120,6 +174,8 @@ fun Tutorial6_1Screen() {
120174 ) {
121175
122176 navigation<RegisterGraph >(
177+ // 🔥🔥 Start destination should be class
178+ // or throws error for Serialized for Companion
123179 startDestination = SessionModel (" " , " " )
124180 ) {
125181 composable<SessionModel > { navBackStackEntry: NavBackStackEntry ->
@@ -161,16 +217,32 @@ fun Tutorial6_1Screen() {
161217
162218 // If registered open Home Screen
163219 if (registerViewModel.loggedIn) {
164- MainContainer { route: Any , navBackStackEntry: NavBackStackEntry ->
220+ MainContainer (
221+ onShowDeeplinkNotification = {
222+ if (hasNotificationPermission) {
223+ showNotification(context)
224+ } else if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
225+ permissionRequest.launch(Manifest .permission.POST_NOTIFICATIONS )
226+ }
227+ }
228+ ) { route: Any , navBackStackEntry: NavBackStackEntry ->
165229 // Navigate only when life cycle is resumed for current screen
166230 if (navBackStackEntry.lifecycleIsResumed()) {
167231 navController.navigate(route = route)
168232 }
169233 }
170234 }
171235 }
236+ }
172237
173- composable<Profile > { navBackStackEntry: NavBackStackEntry ->
238+ navigation<ProfileGraph >(
239+ startDestination = Profile (" " )
240+ ) {
241+ composable<Profile >(
242+ deepLinks = listOf (
243+ navDeepLink<Profile >(basePath = " $uri /profile" )
244+ )
245+ ) { navBackStackEntry: NavBackStackEntry ->
174246 val profile: Profile = navBackStackEntry.toRoute<Profile >()
175247 Screen (profile.toString(), navController)
176248 }
@@ -181,6 +253,7 @@ fun Tutorial6_1Screen() {
181253@SuppressLint(" RestrictedApi" )
182254@Composable
183255private fun MainContainer (
256+ onShowDeeplinkNotification : () -> Unit ,
184257 onGoToProfileScreen : (
185258 route: Any ,
186259 navBackStackEntry: NavBackStackEntry ,
@@ -254,6 +327,7 @@ private fun MainContainer(
254327 ) {
255328 addBottomNavigationGraph(
256329 nestedNavController = nestedNavController,
330+ onShowDeeplinkNotification = onShowDeeplinkNotification,
257331 onGoToProfileScreen = { route, navBackStackEntry ->
258332 onGoToProfileScreen(route, navBackStackEntry)
259333 },
@@ -271,6 +345,7 @@ private fun MainContainer(
271345 */
272346private fun NavGraphBuilder.addBottomNavigationGraph (
273347 nestedNavController : NavHostController ,
348+ onShowDeeplinkNotification : () -> Unit ,
274349 onGoToProfileScreen : (route: Any , navBackStackEntry: NavBackStackEntry ) -> Unit ,
275350 onBottomScreenClick : (route: Any , navBackStackEntry: NavBackStackEntry ) -> Unit ,
276351) {
@@ -339,6 +414,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph(
339414 composable<BottomNavigationRoute .FavoritesRoute > { from: NavBackStackEntry ->
340415 UsersScreen (
341416 homeViewModel = hiltViewModel(),
417+ onShowDeeplinkNotification = onShowDeeplinkNotification,
342418 onProfileClick = { userProfile ->
343419 onGoToProfileScreen(
344420 Profile (" Name: ${userProfile.name} " ),
@@ -386,6 +462,7 @@ private fun SplashScreen(
386462private fun UsersScreen (
387463 homeViewModel : UsersVieModel ,
388464 onProfileClick : (UserProfile ) -> Unit ,
465+ onShowDeeplinkNotification : () -> Unit ,
389466) {
390467
391468 val userList by homeViewModel.profileFlow.collectAsStateWithLifecycle()
@@ -397,17 +474,40 @@ private fun UsersScreen(
397474 ) {
398475
399476 items(items = userList) {
400- Text (
477+
478+ Row (
401479 modifier = Modifier
402480 .fillMaxWidth()
403- .shadow(2 .dp, RoundedCornerShape (16 .dp))
481+ .shadow(4 .dp, RoundedCornerShape (8 .dp))
404482 .background(Color .White )
483+ .fillMaxWidth()
484+ .padding(start = 16 .dp)
405485 .clickable {
406486 onProfileClick(it)
487+ },
488+ verticalAlignment = Alignment .CenterVertically
489+ ) {
490+ Text (
491+ text = " Profile ${it.id} " ,
492+ fontSize = 18 .sp
493+ )
494+
495+ Spacer (modifier = Modifier .weight(1f ))
496+
497+ IconButton (
498+ onClick = {
499+ onShowDeeplinkNotification()
407500 }
408- .padding(16 .dp),
409- text = " name: ${it.name} , id: ${it.id} "
410- )
501+ ) {
502+ Icon (
503+ tint = MaterialTheme .colorScheme.tertiary,
504+ imageVector = Icons .Default .Notifications ,
505+ contentDescription = null
506+ )
507+ }
508+ }
509+
510+
411511 }
412512 }
413513}
0 commit comments