From 74a520e0d1ef2b59c912bc497179b6ac861c8b5e Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月21日 22:09:02 +0300 Subject: [PATCH 001/114] Update Tutorial3_1BottomNavigationBasics.kt --- .../Tutorial3_1BottomNavigationBasics.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_1BottomNavigationBasics.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_1BottomNavigationBasics.kt index d75ceeb7..dec90b5d 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_1BottomNavigationBasics.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_1BottomNavigationBasics.kt @@ -125,8 +125,6 @@ private fun MainContainer() { saveState = true } } - - } } ) @@ -137,24 +135,24 @@ private fun MainContainer() { NavHost( modifier = Modifier.padding(paddingValues), navController = navController, - startDestination = BottomNavigationRoute.HomeRoute + startDestination = BottomNavigationRoute.HomeRoute1 ) { - addHomeGraph(navController) + addBottomNavigationGraph(navController) } } } // 🔥 navController is passed to display back stack in each Composable for demonstration // It's not recommended and only demonstration purposes -private fun NavGraphBuilder.addHomeGraph(navController: NavController) { - composable { from: NavBackStackEntry -> +private fun NavGraphBuilder.addBottomNavigationGraph(navController: NavController) { + composable { from: NavBackStackEntry -> Screen( text = "Home Screen", navController = navController ) } - composable { from: NavBackStackEntry -> + composable { from: NavBackStackEntry -> Screen( text = "Settings Screen", navController = navController @@ -190,7 +188,9 @@ private fun Screen( } Column( - modifier = Modifier.fillMaxSize().padding(16.dp), + modifier = Modifier + .fillMaxSize() + .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( From b377cec9ca3d45a4a2e7f92346cdef78819a251d Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月21日 22:10:00 +0300 Subject: [PATCH 002/114] Update Tutorial3_2BottomNavigationNestedNavigation.kt --- ...rial3_2BottomNavigationNestedNavigation.kt | 189 ++++++++++++------ 1 file changed, 124 insertions(+), 65 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt index 680340d9..fbca8447 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt @@ -3,9 +3,13 @@ package com.smarttoolfactory.tutorial3_1navigation import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.BackHandler +import androidx.collection.forEach import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -15,6 +19,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api @@ -27,9 +33,12 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -46,6 +55,8 @@ import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -53,8 +64,10 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController +import androidx.navigation.get import androidx.navigation.toRoute +@SuppressLint("RestrictedApi") @Preview @Composable fun Tutorial3_2Screen() { @@ -106,12 +119,13 @@ fun Tutorial3_2Screen() { } } +@SuppressLint("RestrictedApi") @Composable private fun MainContainer( - onScreenClick: ( + onGoToProfileScreen: ( route: Any, navBackStackEntry: NavBackStackEntry, - ) -> Unit, + ) -> Unit ) { val items = remember { bottomRouteDataList() @@ -141,11 +155,9 @@ private fun MainContainer( tonalElevation = 4.dp ) { items.forEach { item: BottomRouteData -> - // Checks destination's route with type safety val selected = currentDestination?.hierarchy?.any { it.hasRoute(item.route::class) } == true - NavigationBarItem( selected = selected, icon = { @@ -155,38 +167,19 @@ private fun MainContainer( ) }, onClick = { - // Returns current destinations by parent-child relationship - currentDestination?.hierarchy?.forEach { destination: NavDestination -> - println("HIERARCHY: destination: $destination") - } + nestedNavController.navigate(route = item.route) { + launchSingleTop = true - // This is for not opening same screen if current destination - // is equal to target destination - if (selected.not()) { - - nestedNavController.navigate(route = item.route) { - launchSingleTop = true - - // 🔥 If restoreState = true and saveState = true are commented - // routes other than Home1 are not saved - restoreState = true - - // Pop up backstack to the first destination and save state. - // This makes going back - // to the start destination when pressing back in any other bottom tab. - popUpTo(findStartDestination(nestedNavController.graph).id) { - saveState = true - } - - val startDestinationRoute = - nestedNavController.graph.startDestinationRoute - val startDestinationRecursive = - findStartDestination(nestedNavController.graph).route - println( - "🔥 startDestinationRoute: $startDestinationRoute, " + - "startDestinationRecursive: $startDestinationRecursive\n" + - "navigating target route: ${item.route}" - ) + // 🔥 If restoreState = true and saveState = true are commented + // routes other than Home1 are not saved + restoreState = true + + + // Pop up backstack to the first destination and save state. + // This makes going back + // to the start destination when pressing back in any other bottom tab. + popUpTo(findStartDestination(nestedNavController.graph).id) { + saveState = true } } } @@ -198,20 +191,31 @@ private fun MainContainer( NavHost( modifier = Modifier.padding(paddingValues), navController = nestedNavController, - startDestination = BottomNavigationRoute.HomeRoute + startDestination = BottomNavigationRoute.HomeGraph ) { - addBottomNavigationGraph(nestedNavController) { route, navBackStackEntry -> - onScreenClick(route, navBackStackEntry) - } + addBottomNavigationGraph( + nestedNavController = nestedNavController, + onGoToProfileScreen = { route, navBackStackEntry -> + onGoToProfileScreen(route, navBackStackEntry) + }, + onBottomScreenClick = { route, navBackStackEntry -> + nestedNavController.navigate(route) + } + ) } } } +/** + * @param onGoToProfileScreen lambda for navigating Profile screen from current screen with top NavHostController + * @param onBottomScreenClick lambda for navigating with [nestedNavController] in BottomNavigation + */ private fun NavGraphBuilder.addBottomNavigationGraph( nestedNavController: NavHostController, - onScreenClick: (route: Any, navBackStackEntry: NavBackStackEntry) -> Unit, + onGoToProfileScreen: (route: Any, navBackStackEntry: NavBackStackEntry) -> Unit, + onBottomScreenClick: (route: Any, navBackStackEntry: NavBackStackEntry) -> Unit, ) { - navigation( + navigation( startDestination = BottomNavigationRoute.HomeRoute1 ) { composable { from: NavBackStackEntry -> @@ -219,7 +223,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( text = "Home Screen1", navController = nestedNavController, onClick = { - nestedNavController.navigate(BottomNavigationRoute.HomeRoute2) + onBottomScreenClick(BottomNavigationRoute.HomeRoute2, from) } ) } @@ -229,7 +233,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( text = "Home Screen2", navController = nestedNavController, onClick = { - nestedNavController.navigate(BottomNavigationRoute.HomeRoute3) + onBottomScreenClick(BottomNavigationRoute.HomeRoute3, from) } ) } @@ -242,7 +246,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( } } - navigation( + navigation( startDestination = BottomNavigationRoute.SettingsRoute1 ) { composable { from: NavBackStackEntry -> @@ -250,7 +254,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( text = "Settings Screen", navController = nestedNavController, onClick = { - nestedNavController.navigate(BottomNavigationRoute.SettingsRoute2) + onBottomScreenClick(BottomNavigationRoute.SettingsRoute2, from) } ) } @@ -260,7 +264,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( text = "Settings Screen2", navController = nestedNavController, onClick = { - nestedNavController.navigate(BottomNavigationRoute.SettingsRoute3) + onBottomScreenClick(BottomNavigationRoute.SettingsRoute3, from) } ) } @@ -278,7 +282,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( text = "Favorites Screen", navController = nestedNavController, onClick = { - onScreenClick( + onGoToProfileScreen( Profile("Favorites"), from ) @@ -291,7 +295,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( text = "Notifications Screen", navController = nestedNavController, onClick = { - onScreenClick( + onGoToProfileScreen( Profile("Notifications"), from ) @@ -302,7 +306,7 @@ private fun NavGraphBuilder.addBottomNavigationGraph( @SuppressLint("RestrictedApi") @Composable -private fun Screen( +fun Screen( text: String, navController: NavController, onClick: (() -> Unit)? = null, @@ -349,28 +353,83 @@ private fun Screen( val currentBackStack: List by navController.currentBackStack.collectAsState() - LazyColumn( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + val pagerState = rememberPagerState { + 2 + } + HorizontalPager(state = pagerState) { page -> - // Don't do looped operations in actual code, it's for demonstration - items(items = currentBackStack.reversed()) { + val headerText = if (page == 0) "Current Back stack(reversed)" else "Current hierarchy" + Column { Text( - text = it.destination.route - ?.replace("$packageName.", "") - ?.replace( - "BottomNavigationRoute.", - "" - ) ?: it.destination.displayName, modifier = Modifier - .shadow(4.dp, RoundedCornerShape(8.dp)) - .background(Color.White) .fillMaxWidth() - .padding(16.dp), - fontSize = 18.sp + .padding(bottom = 8.dp), + text = headerText, + fontSize = 20.sp, + fontWeight = FontWeight.Bold ) + + val destinations = if (page == 0) { + currentBackStack.reversed().map { it.destination } + } else { + navController.currentDestination?.hierarchy?.toList() ?: listOf() + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + + items(items = destinations) { destination: NavDestination -> + + if (destination is NavGraph) { + MainText(destination, packageName) + destination.nodes.forEach { _, value -> + SubItemText(value, packageName) + } + + } else { + MainText(destination, packageName) + } + } + } + } } } } + +@SuppressLint("RestrictedApi") +@Composable +private fun SubItemText(value: NavDestination, packageName: String?) { + Text( + text = value.route + ?.replace("$packageName.", "") + ?.replace("BottomNavigationRoute.", "") + ?: value.displayName, + modifier = Modifier + .padding(start = 8.dp, bottom = 2.dp) + .shadow(2.dp, RoundedCornerShape(8.dp)) + .background(Color.White) + .fillMaxWidth() + .padding(8.dp), + fontSize = 12.sp + ) +} + +@SuppressLint("RestrictedApi") +@Composable +private fun MainText(destination: NavDestination, packageName: String?) { + Text( + text = destination.route + ?.replace("$packageName.", "") + ?.replace("BottomNavigationRoute.", "") + ?: destination.displayName, + modifier = Modifier + .shadow(4.dp, RoundedCornerShape(8.dp)) + .background(Color.White) + .fillMaxWidth() + .padding(16.dp), + fontSize = 18.sp + ) +} From 6bd6c9cbe6aade3f7a3ac82920db4b4f7c2bda65 Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月21日 22:11:04 +0300 Subject: [PATCH 003/114] Update AndroidManifest.xml --- .../src/main/AndroidManifest.xml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/AndroidManifest.xml b/Tutorial3-1Navigation/src/main/AndroidManifest.xml index 20eb407e..439bd4b0 100644 --- a/Tutorial3-1Navigation/src/main/AndroidManifest.xml +++ b/Tutorial3-1Navigation/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ + @@ -16,10 +17,19 @@ android:theme="@style/Theme.ComposeTutorials"> - + + + + + + + + + + - \ No newline at end of file + From 3bf40384f12d00f53ed8d4ba44f364defd28805f Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月21日 22:15:59 +0300 Subject: [PATCH 004/114] Create Tutorial4_1Deeplink.kt --- .../Tutorial4_1Deeplink.kt | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt new file mode 100644 index 00000000..c394c4d2 --- /dev/null +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt @@ -0,0 +1,236 @@ +package com.smarttoolfactory.tutorial3_1navigation + +import android.Manifest +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.TaskStackBuilder +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService +import androidx.core.net.toUri +import androidx.navigation.NavBackStackEntry +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navDeepLink +import androidx.navigation.toRoute + +/* + Steps to add deeplink to Profile Screen + 1- Add deeplink scheme and host and intent filter to manifest + 2- Add url to composable as + composable( + deepLinks = listOf( + navDeepLink(basePath = "$uri/profile") + ) + 3- Test with adb by calling snipped below in terminal + adb shell am start -W -a android.intent.action.VIEW -d "https://www.example.com/profile/2" + */ + +@Preview +@Composable +fun Tutorial4_1Screen() { + MainContainer() +} + +@Composable +private fun MainContainer() { + val navController = rememberNavController() + + val context = LocalContext.current + + var hasNotificationPermission by remember { + mutableStateOf( + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { + + ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else true + ) + } + + val permissionRequest = + rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { result -> + hasNotificationPermission = result + + if (hasNotificationPermission) { + showNotification(context) + } + } + + NavHost( + modifier = Modifier.fillMaxSize(), + navController = navController, + startDestination = Splash + ) { + + composable { navBackStackEntry: NavBackStackEntry -> + SplashScreen { + navController.navigate(Home) { + popUpTo() { + inclusive = true + } + } + } + } + + composable { navBackStackEntry: NavBackStackEntry -> + HomeScreen { profile: Profile -> + if (hasNotificationPermission) { + showNotification(context) + } else { + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { + permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + } + } + + composable( + deepLinks = listOf( + navDeepLink(basePath = "$uri/profile") + ) + ) { navBackStackEntry: NavBackStackEntry -> + val profile: Profile = navBackStackEntry.toRoute() + Screen(profile.toString(), navController) + } + } +} + +@Composable +fun SplashScreen( + onClick: () -> Unit +) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = "Splash Screen", fontSize = 34.sp, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = onClick) { + Text(text = "Go to Home") + } + } +} + +private fun showNotification(context: Context) { + val id = "exampleId" + + val deepLinkIntent = Intent( + Intent.ACTION_VIEW, + "$uri/profile/$id".toUri(), + context, + MainActivity::class.java + ) + + val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder + .create(context).run { + addNextIntentWithParentStack(deepLinkIntent) + getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE) + } + + + val notification: Notification = NotificationCompat.Builder( + context, + "channelId" + ) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle("Go to app") + .setContentText("Click to open Profile") + .setPriority(NotificationCompat.PRIORITY_MAX) + .setAutoCancel(true) + .setContentIntent(deepLinkPendingIntent) + .build() + + val notificationManager = context.getSystemService() as NotificationManager? + + notificationManager?.run { + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) { + this.createNotificationChannel( + NotificationChannel("channelId", "name", NotificationManager.IMPORTANCE_DEFAULT) + ) + } + this.notify(1, notification) + } + +} + +@Composable +private fun HomeScreen( + onClick: (Profile) -> Unit, +) { + Column( + modifier = Modifier.fillMaxSize() + ) { + val list = remember { + List(20) { + Profile(it.toString()) + } + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(list) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onClick(it) + } + ) { + Text( + text = "Profile ${it.id}", + modifier = Modifier + .shadow(4.dp, RoundedCornerShape(8.dp)) + .background(Color.White) + .fillMaxWidth() + .padding(16.dp), + fontSize = 18.sp + ) + } + } + } + } +} From 674e666a4c47237fdaa7804c90987d8352265b3d Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月25日 09:32:12 +0300 Subject: [PATCH 005/114] update Tutorial3_2BottomNavigationNestedNavigation --- .../src/main/AndroidManifest.xml | 4 +- .../tutorial3_1navigation/MainActivity.kt | 5 +- ...rial3_2BottomNavigationNestedNavigation.kt | 84 +++++++++++++------ 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/AndroidManifest.xml b/Tutorial3-1Navigation/src/main/AndroidManifest.xml index 439bd4b0..bf87bdb8 100644 --- a/Tutorial3-1Navigation/src/main/AndroidManifest.xml +++ b/Tutorial3-1Navigation/src/main/AndroidManifest.xml @@ -20,13 +20,13 @@ - + - + diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/MainActivity.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/MainActivity.kt index dccc80d0..1fee08e1 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/MainActivity.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/MainActivity.kt @@ -18,6 +18,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { + ComposeTutorialsTheme { Scaffold { innerPadding -> Surface( @@ -27,7 +28,9 @@ class MainActivity : ComponentActivity() { ) { // Tutorial1Screen() // Tutorial2Screen() - Tutorial3_1Screen() +// Tutorial3_1Screen() +// Tutorial3_2Screen() + Tutorial4_1Screen() } } } diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt index fbca8447..44ba2fa4 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt @@ -3,16 +3,14 @@ package com.smarttoolfactory.tutorial3_1navigation import android.annotation.SuppressLint -import android.os.Bundle -import androidx.activity.compose.BackHandler import androidx.collection.forEach import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -33,15 +31,13 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color @@ -56,7 +52,6 @@ import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph -import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -64,7 +59,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController -import androidx.navigation.get import androidx.navigation.toRoute @SuppressLint("RestrictedApi") @@ -399,37 +393,77 @@ fun Screen( } } -@SuppressLint("RestrictedApi") @Composable -private fun SubItemText(value: NavDestination, packageName: String?) { - Text( - text = value.route - ?.replace("$packageName.", "") - ?.replace("BottomNavigationRoute.", "") - ?: value.displayName, +private fun SubItemText(destination: NavDestination, packageName: String?) { + Row( modifier = Modifier .padding(start = 8.dp, bottom = 2.dp) .shadow(2.dp, RoundedCornerShape(8.dp)) .background(Color.White) .fillMaxWidth() .padding(8.dp), - fontSize = 12.sp - ) + verticalAlignment = Alignment.Bottom + ) { + + Text( + text = getDestinationFormattedText( + destination, + packageName + ), + fontSize = 12.sp + ) + +// destination.parent?.let { +// Text( +// text = ", parent: " + getGraphFormattedText(it, packageName), +// fontSize = 10.sp +// ) +// } + } } -@SuppressLint("RestrictedApi") @Composable private fun MainText(destination: NavDestination, packageName: String?) { - Text( - text = destination.route - ?.replace("$packageName.", "") - ?.replace("BottomNavigationRoute.", "") - ?: destination.displayName, + + + Row( modifier = Modifier .shadow(4.dp, RoundedCornerShape(8.dp)) .background(Color.White) .fillMaxWidth() .padding(16.dp), - fontSize = 18.sp - ) + verticalAlignment = Alignment.Bottom + ) { + Text( + text = getDestinationFormattedText( + destination, + packageName + ), + fontSize = 18.sp + ) +// destination.parent?.let { +// Text( +// text = ", parent: " + getGraphFormattedText(it, packageName), +// fontSize = 14.sp +// ) +// } + } } + +@SuppressLint("RestrictedApi") +private fun getDestinationFormattedText( + destination: NavDestination, + packageName: String?, +) = (destination.route + ?.replace("$packageName.", "") + ?.replace("BottomNavigationRoute.", "") + ?: (destination.displayName)) + +@SuppressLint("RestrictedApi") +private fun getGraphFormattedText( + destination: NavGraph, + packageName: String?, +) = (destination.route + ?.replace("$packageName.", "") + ?.replace("BottomNavigationRoute.", "") + ?: (destination.displayName)) From 9c8f363e9d80cd508b2da6c4a0eac4950cfd4dd9 Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月25日 09:36:00 +0300 Subject: [PATCH 006/114] change deeplink scheme --- .../smarttoolfactory/tutorial3_1navigation/Routes.kt | 2 +- .../tutorial3_1navigation/Tutorial4_1Deeplink.kt | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt index db6198d9..33bb6200 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt @@ -20,7 +20,7 @@ object Home @Serializable data class Profile(val id: String) -const val uri = "https://www.example.com" +const val uri = "test://www.example.com" @Serializable object RouteA diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt index c394c4d2..8d9032a7 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt @@ -61,7 +61,7 @@ import androidx.navigation.toRoute navDeepLink(basePath = "$uri/profile") ) 3- Test with adb by calling snipped below in terminal - adb shell am start -W -a android.intent.action.VIEW -d "https://www.example.com/profile/2" + adb shell am start -W -a android.intent.action.VIEW -d "test://www.example.com/profile/2" */ @Preview @@ -106,7 +106,7 @@ private fun MainContainer() { composable { navBackStackEntry: NavBackStackEntry -> SplashScreen { navController.navigate(Home) { - popUpTo() { + popUpTo { inclusive = true } } @@ -117,10 +117,8 @@ private fun MainContainer() { HomeScreen { profile: Profile -> if (hasNotificationPermission) { showNotification(context) - } else { - if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { - permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) - } + } else if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { + permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) } } } @@ -138,7 +136,7 @@ private fun MainContainer() { @Composable fun SplashScreen( - onClick: () -> Unit + onClick: () -> Unit, ) { Column( modifier = Modifier.fillMaxSize(), From 3aa2142001b254cb63bd6eb1da23e3b32467e832 Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月26日 16:18:30 +0300 Subject: [PATCH 007/114] add notification button --- .../tutorial3_1navigation/Routes.kt | 9 +++ .../Tutorial4_1Deeplink.kt | 74 +++++++++++++------ 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt index 33bb6200..c9299353 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Routes.kt @@ -12,10 +12,19 @@ import kotlinx.serialization.Serializable @Serializable object Splash +@Serializable +object SplashGraph + // Define a home route that doesn't take any arguments @Serializable object Home +@Serializable +object HomeGraph + +@Serializable +object ProfileGraph + // Define a profile route that takes an ID @Serializable data class Profile(val id: String) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt index 8d9032a7..5c6be623 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -25,7 +26,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Notifications import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -48,6 +54,7 @@ import androidx.core.net.toUri import androidx.navigation.NavBackStackEntry import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController import androidx.navigation.navDeepLink import androidx.navigation.toRoute @@ -102,7 +109,6 @@ private fun MainContainer() { navController = navController, startDestination = Splash ) { - composable { navBackStackEntry: NavBackStackEntry -> SplashScreen { navController.navigate(Home) { @@ -113,23 +119,31 @@ private fun MainContainer() { } } - composable { navBackStackEntry: NavBackStackEntry -> - HomeScreen { profile: Profile -> - if (hasNotificationPermission) { - showNotification(context) - } else if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { - permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) + navigation( + startDestination = Home + ) { + composable { navBackStackEntry: NavBackStackEntry -> + HomeScreen( + onClick = { profile: Profile -> + navController.navigate(profile) + } + ) { _: Profile -> + if (hasNotificationPermission) { + showNotification(context) + } else if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { + permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) + } } } - } - composable( - deepLinks = listOf( - navDeepLink(basePath = "$uri/profile") - ) - ) { navBackStackEntry: NavBackStackEntry -> - val profile: Profile = navBackStackEntry.toRoute() - Screen(profile.toString(), navController) + composable( + deepLinks = listOf( + navDeepLink(basePath = "$uri/profile") + ) + ) { navBackStackEntry: NavBackStackEntry -> + val profile: Profile = navBackStackEntry.toRoute() + Screen(profile.toString(), navController) + } } } } @@ -196,6 +210,7 @@ private fun showNotification(context: Context) { @Composable private fun HomeScreen( onClick: (Profile) -> Unit, + onOpenDeeplink: (Profile) -> Unit, ) { Column( modifier = Modifier.fillMaxSize() @@ -208,25 +223,40 @@ private fun HomeScreen( LazyColumn( modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(16.dp) + verticalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(16.dp) ) { items(list) { Row( modifier = Modifier .fillMaxWidth() + .shadow(4.dp, RoundedCornerShape(8.dp)) + .background(Color.White) + .fillMaxWidth() + .padding(start = 16.dp) .clickable { onClick(it) - } + }, + verticalAlignment = Alignment.CenterVertically ) { Text( text = "Profile ${it.id}", - modifier = Modifier - .shadow(4.dp, RoundedCornerShape(8.dp)) - .background(Color.White) - .fillMaxWidth() - .padding(16.dp), fontSize = 18.sp ) + + Spacer(modifier = Modifier.weight(1f)) + + IconButton( + onClick = { + onOpenDeeplink(it) + } + ) { + Icon( + tint = MaterialTheme.colorScheme.tertiary, + imageVector = Icons.Default.Notifications, + contentDescription = null + ) + } } } } From efea98b63e63a85b80f7dc1bd4ca6d769e35905d Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月26日 19:37:10 +0300 Subject: [PATCH 008/114] update bottom selection index logic --- ...rial3_2BottomNavigationNestedNavigation.kt | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt index 44ba2fa4..92d0f0c1 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial3_2BottomNavigationNestedNavigation.kt @@ -119,17 +119,33 @@ private fun MainContainer( onGoToProfileScreen: ( route: Any, navBackStackEntry: NavBackStackEntry, - ) -> Unit + ) -> Unit, ) { val items = remember { bottomRouteDataList() } val nestedNavController = rememberNavController() - val navBackStackEntry: NavBackStackEntry? by nestedNavController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination + var selectedIndex by remember { + mutableIntStateOf(0) + } + + currentDestination?.let { + selectedIndex = + if (currentDestination.hasRoute(BottomNavigationRoute.NotificationRoute::class)) { + 3 + } else if (currentDestination.hasRoute(BottomNavigationRoute.FavoritesRoute::class)) { + 2 + } else if (currentDestination.hierarchy.any { it.hasRoute(BottomNavigationRoute.SettingsGraph::class) }) { + 1 + } else { + 0 + } + } + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { @@ -148,10 +164,15 @@ private fun MainContainer( modifier = Modifier.height(56.dp), tonalElevation = 4.dp ) { - items.forEach { item: BottomRouteData -> - // Checks destination's route with type safety - val selected = - currentDestination?.hierarchy?.any { it.hasRoute(item.route::class) } == true + items.forEachIndexed { index, item: BottomRouteData -> + + // This doesn't work when Home2, Home3 selected because item.route + // checks Home1, Home2, Settings1, Setting2 +// val selected = +// currentDestination?.hierarchy?.any { it.hasRoute(item.route::class) } == true + + val selected = selectedIndex == index + NavigationBarItem( selected = selected, icon = { @@ -168,7 +189,6 @@ private fun MainContainer( // routes other than Home1 are not saved restoreState = true - // Pop up backstack to the first destination and save state. // This makes going back // to the start destination when pressing back in any other bottom tab. From 9477f964c109c27e40eba7d089271536e8366019 Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月27日 17:55:34 +0300 Subject: [PATCH 009/114] update Tutorial4_1Deeplink with dynamic start destination --- .../Tutorial4_1Deeplink.kt | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt index 5c6be623..cdfb8dba 100644 --- a/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt +++ b/Tutorial3-1Navigation/src/main/java/com/smarttoolfactory/tutorial3_1navigation/Tutorial4_1Deeplink.kt @@ -9,7 +9,9 @@ import android.app.TaskStackBuilder import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Build +import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background @@ -52,6 +54,8 @@ import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.core.net.toUri import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.navigation @@ -104,46 +108,63 @@ private fun MainContainer() { } } + val deeplink: Uri? = (LocalContext.current as? MainActivity)?.intent?.data + val isDeeplink = deeplink != null + + println("Deeplink: $deeplink") + NavHost( modifier = Modifier.fillMaxSize(), navController = navController, - startDestination = Splash + // 🔥 Change start destination based on if deeplink is available + startDestination = if (isDeeplink) HomeGraph else Splash ) { - composable { navBackStackEntry: NavBackStackEntry -> - SplashScreen { - navController.navigate(Home) { - popUpTo { - inclusive = true - } + addNavGraph(navController, hasNotificationPermission, context, permissionRequest) + } +} + +private fun NavGraphBuilder.addNavGraph( + navController: NavHostController, + hasNotificationPermission: Boolean, + context: Context, + permissionRequest: ManagedActivityResultLauncher, +) { + composable { navBackStackEntry: NavBackStackEntry -> + + SplashScreen { + navController.navigate(Home) { + popUpTo { + inclusive = true } } } + } - navigation( - startDestination = Home - ) { - composable { navBackStackEntry: NavBackStackEntry -> - HomeScreen( - onClick = { profile: Profile -> - navController.navigate(profile) - } - ) { _: Profile -> - if (hasNotificationPermission) { - showNotification(context) - } else if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { - permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) - } + navigation( + startDestination = Home + ) { + composable { navBackStackEntry: NavBackStackEntry -> + HomeScreen( + onClick = { profile: Profile -> + navController.navigate(profile) + } + ) { _: Profile -> + if (hasNotificationPermission) { + showNotification(context) + } else if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { + permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) } } + } - composable( - deepLinks = listOf( - navDeepLink(basePath = "$uri/profile") - ) - ) { navBackStackEntry: NavBackStackEntry -> - val profile: Profile = navBackStackEntry.toRoute() - Screen(profile.toString(), navController) - } + composable( + deepLinks = listOf( + navDeepLink(basePath = "$uri/profile") + ) + ) { navBackStackEntry: NavBackStackEntry -> + + val profile: Profile = navBackStackEntry.toRoute() + Screen(profile.toString(), navController) } } } From d8080e43e6b18acd3bf2c54ae24ae11abf47cadf Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月29日 12:31:07 +0300 Subject: [PATCH 010/114] add inner shadow sample --- .../Tutorial6_36InnerShadow.kt | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_36InnerShadow.kt diff --git a/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_36InnerShadow.kt b/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_36InnerShadow.kt new file mode 100644 index 00000000..4ba498fc --- /dev/null +++ b/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_36InnerShadow.kt @@ -0,0 +1,143 @@ +package com.smarttoolfactory.tutorial1_1basics.chapter6_graphics + +import android.graphics.BlurMaskFilter +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Slider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + + +@Preview +@Composable +fun InnerShadowSample() { + + Column( + modifier = Modifier.fillMaxSize() + .background(Color(0xff00796B)) + .padding(16.dp) + ) { + + var blurRadiusWhite by remember { + mutableFloatStateOf(5f) + } + + var blurRadiusBlack by remember { + mutableFloatStateOf(4f) + } + + Text("Blur white: $blurRadiusWhite") + + + Slider( + value = blurRadiusWhite, + onValueChange = { + blurRadiusWhite = it + }, + valueRange = 1f..20f + ) + + Text("Blur black: $blurRadiusBlack") + Slider( + value = blurRadiusBlack, + onValueChange = { + blurRadiusBlack = it + }, + valueRange = 1f..20f + ) + + + Row( + modifier = Modifier + .innerShadow( + shape = RoundedCornerShape(16.dp), + color = Color.White.copy(.8f), + x = (-2).dp, + y = (-2).dp, + blurRadius = blurRadiusWhite.dp + ) + .innerShadow( + shape = RoundedCornerShape(16.dp), + color = Color.Black, + x = 2.dp, + y = 2.dp, + blurRadius = (blurRadiusBlack).dp + ) + .background(Color(0xff009688), RoundedCornerShape(16.dp)) + .padding(16.dp) + ) { + Text( + text = "Hello World", + fontSize = 30.sp, + + ) + } + } +} + +fun Modifier.innerShadow( + shape: Shape, + color: Color = Color.Black, + x: Dp = 2.dp, + y: Dp = 2.dp, + blurRadius: Dp = 4.dp, +) = composed { + val paint = remember { + Paint() + } + + println("Paint: $paint") + + drawWithContent { + drawContent() + + val outline = shape.createOutline(size, layoutDirection, this) + + paint.color = color + + with(drawContext.canvas) { + saveLayer(size.toRect(), paint) + + drawOutline(outline, paint) + + val frameworkPaint = paint.asFrameworkPaint() + + frameworkPaint.apply { + xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) + maskFilter = + BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL) + } + + paint.color = Color.Black + + translate(x.toPx(), y.toPx()) + drawOutline(outline, paint) + + frameworkPaint.xfermode = null + frameworkPaint.maskFilter = null + restore() + } + } +} \ No newline at end of file From e5e1c5626b3c375f09e910a16a8530aebdc9326b Mon Sep 17 00:00:00 2001 From: Smart Tool Factory Date: 2024年10月29日 14:39:29 +0300 Subject: [PATCH 011/114] update gradle and catalogs --- Tutorial1-1Basics/build.gradle.kts | 2 +- .../src/main/res/values-night/themes.xml | 10 +-- .../src/main/res/values/themes.xml | 7 +-- Tutorial2-1Unit-Testing/build.gradle.kts | 19 +++--- Tutorial2-2UI-Testing/build.gradle.kts | 10 +-- Tutorial3-1Navigation/build.gradle.kts | 4 +- gradle/libs.versions.toml | 62 +++++++------------ 7 files changed, 41 insertions(+), 73 deletions(-) diff --git a/Tutorial1-1Basics/build.gradle.kts b/Tutorial1-1Basics/build.gradle.kts index 5f93c95f..871806d5 100644 --- a/Tutorial1-1Basics/build.gradle.kts +++ b/Tutorial1-1Basics/build.gradle.kts @@ -70,7 +70,7 @@ dependencies { implementation(libs.kotlinx.coroutines.android) implementation(libs.androidx.appcompat) - implementation(libs.material.v1120) + implementation(libs.androidx.compose.material) implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) diff --git a/Tutorial1-1Basics/src/main/res/values-night/themes.xml b/Tutorial1-1Basics/src/main/res/values-night/themes.xml index 3c661353..2e4107ed 100644 --- a/Tutorial1-1Basics/src/main/res/values-night/themes.xml +++ b/Tutorial1-1Basics/src/main/res/values-night/themes.xml @@ -1,14 +1,8 @@ - + -