diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ca31724..d601514 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("skydoves.pokedex.android.application.compose") id("skydoves.pokedex.android.hilt") id("skydoves.pokedex.spotless") - id("kotlin-parcelize") + alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.baselineprofile) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5d2c1bf..7c784a0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ xmlns:tools="http://schemas.android.com/tools"> override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt index ffd8df3..abdf151 100644 --- a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt +++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavHost.kt @@ -20,14 +20,14 @@ import androidx.compose.animation.SharedTransitionLayout import androidx.compose.runtime.Composable import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import com.skydoves.pokedex.compose.core.navigation.PokedexScreens +import com.skydoves.pokedex.compose.core.navigation.PokedexScreen @Composable fun PokedexNavHost(navHostController: NavHostController) { SharedTransitionLayout { NavHost( navController = navHostController, - startDestination = PokedexScreens.Home.route, + startDestination = PokedexScreen.Home, ) { pokedexNavigation() } diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt index 309b755..7c9232c 100644 --- a/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt +++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/navigation/PokedexNavigation.kt @@ -19,19 +19,18 @@ package com.skydoves.pokedex.compose.navigation import androidx.compose.animation.SharedTransitionScope import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.skydoves.pokedex.compose.core.navigation.PokedexScreens +import com.skydoves.pokedex.compose.core.navigation.PokedexScreen import com.skydoves.pokedex.compose.feature.details.PokedexDetails import com.skydoves.pokedex.compose.feature.home.PokedexHome context(SharedTransitionScope) fun NavGraphBuilder.pokedexNavigation() { - composable(route = PokedexScreens.Home.name) { + composable { PokedexHome(this) } - composable( - route = PokedexScreens.Details.name, - arguments = PokedexScreens.Details.navArguments, + composable( + typeMap = PokedexScreen.Details.typeMap, ) { PokedexDetails(this) } diff --git a/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt b/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt index 0ed536d..d6c77e1 100644 --- a/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt +++ b/app/src/main/kotlin/com/skydoves/pokedex/compose/ui/PokedexMain.kt @@ -21,10 +21,11 @@ import androidx.compose.runtime.LaunchedEffect import androidx.navigation.compose.rememberNavController import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme import com.skydoves.pokedex.compose.core.navigation.AppComposeNavigator +import com.skydoves.pokedex.compose.core.navigation.PokedexScreen import com.skydoves.pokedex.compose.navigation.PokedexNavHost @Composable -fun PokedexMain(composeNavigator: AppComposeNavigator) { +fun PokedexMain(composeNavigator: AppComposeNavigator) { PokedexTheme { val navHostController = rememberNavController() diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index d3eb26d..be34d0e 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -18,7 +18,7 @@ plugins { id("skydoves.pokedex.android.library") id("skydoves.pokedex.android.hilt") id("skydoves.pokedex.spotless") - id("com.google.devtools.ksp") + alias(libs.plugins.ksp) } android { diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 87792c8..8482b54 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -16,9 +16,9 @@ plugins { id("skydoves.pokedex.android.library") - id("org.jetbrains.kotlin.plugin.serialization") - id("kotlin-parcelize") - id("com.google.devtools.ksp") + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.ksp) id("skydoves.pokedex.spotless") } diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index 9c73538..bee1584 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("skydoves.pokedex.android.library") id("skydoves.pokedex.android.library.compose") - id("org.jetbrains.kotlin.plugin.serialization") + alias(libs.plugins.kotlinx.serialization) id("skydoves.pokedex.android.hilt") id("skydoves.pokedex.spotless") } @@ -13,10 +13,9 @@ android { dependencies { implementation(projects.core.model) + implementation(libs.androidx.core) implementation(libs.kotlinx.coroutines.android) - implementation(libs.androidx.compose.ui) - implementation(libs.androidx.compose.animation) api(libs.androidx.navigation.compose) // json parsing diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt index 7035044..31b5158 100644 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/LocalComposeNavigator.kt @@ -21,7 +21,7 @@ import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.compositionLocalOf -public val LocalComposeNavigator: ProvidableCompositionLocal = +public val LocalComposeNavigator: ProvidableCompositionLocal> = compositionLocalOf { error( "No AppComposeNavigator provided! " + @@ -32,7 +32,7 @@ public val LocalComposeNavigator: ProvidableCompositionLocal @Composable @ReadOnlyComposable get() = LocalComposeNavigator.current diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt index 4988f5c..9e9ed28 100644 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationCommand.kt @@ -18,19 +18,20 @@ package com.skydoves.pokedex.compose.core.navigation import androidx.navigation.NavOptions -sealed class NavigationCommand { - data object NavigateUp : NavigationCommand() +sealed interface NavigationCommand { + data object NavigateUp : NavigationCommand } -sealed class ComposeNavigationCommand : NavigationCommand() { - data class NavigateToRoute(val route: String, val options: NavOptions? = null) : - ComposeNavigationCommand() +sealed interface ComposeNavigationCommand : NavigationCommand { + data class NavigateToRoute(val route: T, val options: NavOptions? = null) : + ComposeNavigationCommand - data class NavigateUpWithResult( + data class NavigateUpWithResult( val key: String, - val result: T, - val route: String? = null, - ) : ComposeNavigationCommand() + val result: R, + val route: T? = null, + ) : ComposeNavigationCommand - data class PopUpToRoute(val route: String, val inclusive: Boolean) : ComposeNavigationCommand() + data class PopUpToRoute(val route: T, val inclusive: Boolean) : + ComposeNavigationCommand } diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationModule.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationModule.kt index 638f630..4d4e13e 100755 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationModule.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/NavigationModule.kt @@ -24,11 +24,11 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal abstract class NavigationModule { +internal interface NavigationModule { @Binds @Singleton - abstract fun provideComposeNavigator( + fun provideComposeNavigator( pokedexComposeNavigator: PokedexComposeNavigator, - ): AppComposeNavigator + ): AppComposeNavigator } diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt index 83f791c..e171378 100644 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Navigator.kt @@ -35,12 +35,12 @@ abstract class Navigator { } } -abstract class AppComposeNavigator : Navigator() { - abstract fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)? = null) - abstract fun navigateBackWithResult(key: String, result: T, route: String?) +abstract class AppComposeNavigator : Navigator() { + abstract fun navigate(route: T, optionsBuilder: (NavOptionsBuilder.() -> Unit)? = null) + abstract fun navigateBackWithResult(key: String, result: R, route: T?) - abstract fun popUpTo(route: String, inclusive: Boolean) - abstract fun navigateAndClearBackStack(route: String) + abstract fun popUpTo(route: T, inclusive: Boolean) + abstract fun navigateAndClearBackStack(route: T) suspend fun handleNavigationCommands(navController: NavController) { navigationCommands @@ -51,24 +51,24 @@ abstract class AppComposeNavigator : Navigator() { private fun NavController.handleComposeNavigationCommand(navigationCommand: NavigationCommand) { when (navigationCommand) { - is ComposeNavigationCommand.NavigateToRoute -> { + is ComposeNavigationCommand.NavigateToRoute<*> -> { navigate(navigationCommand.route, navigationCommand.options) } NavigationCommand.NavigateUp -> navigateUp() - is ComposeNavigationCommand.PopUpToRoute -> popBackStack( + is ComposeNavigationCommand.PopUpToRoute<*> -> popBackStack( navigationCommand.route, navigationCommand.inclusive, ) - is ComposeNavigationCommand.NavigateUpWithResult<*> -> { + is ComposeNavigationCommand.NavigateUpWithResult<*, *> -> { navUpWithResult(navigationCommand) } } } private fun NavController.navUpWithResult( - navigationCommand: ComposeNavigationCommand.NavigateUpWithResult<*>, + navigationCommand: ComposeNavigationCommand.NavigateUpWithResult<*, *>, ) { val backStackEntry = navigationCommand.route?.let { getBackStackEntry(it) } @@ -80,8 +80,6 @@ abstract class AppComposeNavigator : Navigator() { navigationCommand.route?.let { popBackStack(it, false) - } ?: run { - navigateUp() - } + } ?: navigateUp() } } diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt index d733de4..54600d8 100644 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexComposeNavigator.kt @@ -20,14 +20,14 @@ import androidx.navigation.NavOptionsBuilder import androidx.navigation.navOptions import javax.inject.Inject -class PokedexComposeNavigator @Inject constructor() : AppComposeNavigator() { +class PokedexComposeNavigator @Inject constructor() : AppComposeNavigator() { - override fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)?) { + override fun navigate(route: PokedexScreen, optionsBuilder: (NavOptionsBuilder.() -> Unit)?) { val options = optionsBuilder?.let { navOptions(it) } navigationCommands.tryEmit(ComposeNavigationCommand.NavigateToRoute(route, options)) } - override fun navigateAndClearBackStack(route: String) { + override fun navigateAndClearBackStack(route: PokedexScreen) { navigationCommands.tryEmit( ComposeNavigationCommand.NavigateToRoute( route, @@ -38,11 +38,11 @@ class PokedexComposeNavigator @Inject constructor() : AppComposeNavigator() { ) } - override fun popUpTo(route: String, inclusive: Boolean) { + override fun popUpTo(route: PokedexScreen, inclusive: Boolean) { navigationCommands.tryEmit(ComposeNavigationCommand.PopUpToRoute(route, inclusive)) } - override fun navigateBackWithResult(key: String, result: T, route: String?) { + override fun navigateBackWithResult(key: String, result: R, route: PokedexScreen?) { navigationCommands.tryEmit( ComposeNavigationCommand.NavigateUpWithResult( key = key, diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Parcelable.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt similarity index 63% rename from core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Parcelable.kt rename to core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt index fc4bd47..e6ee687 100644 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/Parcelable.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreen.kt @@ -16,14 +16,18 @@ package com.skydoves.pokedex.compose.core.navigation -import android.os.Build -import android.os.Bundle -import android.os.Parcelable +import com.skydoves.pokedex.compose.core.model.Pokemon +import kotlinx.serialization.Serializable +import kotlin.reflect.typeOf -inline fun Bundle.parcelable(key: String): T? = when { - Build.VERSION.SDK_INT >= 33 -> getParcelable(key, T::class.java) - else -> - @Suppress("DEPRECATION") - getParcelable(key) - as? T +sealed interface PokedexScreen { + @Serializable + data object Home : PokedexScreen + + @Serializable + data class Details(val pokemon: Pokemon) : PokedexScreen { + companion object { + val typeMap = mapOf(typeOf() to PokemonType) + } + } } diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreens.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreens.kt deleted file mode 100644 index 832cd76..0000000 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokedexScreens.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Designed and developed by 2024 skydoves (Jaewoong Eum) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.skydoves.pokedex.compose.core.navigation - -import androidx.navigation.NamedNavArgument -import androidx.navigation.navArgument -import com.skydoves.pokedex.compose.core.model.Pokemon - -sealed class PokedexScreens( - val route: String, - val navArguments: List = emptyList(), -) { - val name: String = route.appendArguments(navArguments) - - data object Home : PokedexScreens("home") - - data object Details : PokedexScreens( - route = "details", - navArguments = listOf( - navArgument("pokemon") { - type = PokemonType() - nullable = false - }, - ), - ) { - fun createRoute(pokemon: Pokemon) = - name.replace("{${navArguments.first().name}}", PokemonType.encodeToString(pokemon)) - } -} - -private fun String.appendArguments(navArguments: List): String { - val mandatoryArguments = navArguments.filter { it.argument.defaultValue == null } - .takeIf { it.isNotEmpty() } - ?.joinToString(separator = "/", prefix = "/") { "{${it.name}}" } - .orEmpty() - val optionalArguments = navArguments.filter { it.argument.defaultValue != null } - .takeIf { it.isNotEmpty() } - ?.joinToString(separator = "&", prefix = "?") { "${it.name}={${it.name}}" } - .orEmpty() - return "$this$mandatoryArguments$optionalArguments" -} diff --git a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokemonType.kt b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokemonType.kt index 9b54651..4a2c521 100644 --- a/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokemonType.kt +++ b/core/navigation/src/main/kotlin/com/skydoves/pokedex/compose/core/navigation/PokemonType.kt @@ -18,27 +18,24 @@ package com.skydoves.pokedex.compose.core.navigation import android.net.Uri import android.os.Bundle +import androidx.core.os.BundleCompat import androidx.navigation.NavType import com.skydoves.pokedex.compose.core.model.Pokemon import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -class PokemonType : NavType(isNullableAllowed = false) { +object PokemonType : NavType(isNullableAllowed = false) { - override fun get(bundle: Bundle, key: String): Pokemon? = bundle.parcelable(key) - - override fun put(bundle: Bundle, key: String, value: Pokemon?) { + override fun put(bundle: Bundle, key: String, value: Pokemon) { bundle.putParcelable(key, value) } + override fun get(bundle: Bundle, key: String): Pokemon? = + BundleCompat.getParcelable(bundle, key, Pokemon::class.java) + override fun parseValue(value: String): Pokemon { - val decoded = Uri.decode(value) - return Json.decodeFromString(decoded)!! + return Json.decodeFromString(Uri.decode(value)) } - companion object { - fun encodeToString(pokemon: Pokemon): String { - return Uri.encode(Json.encodeToString(pokemon)) - } - } + override fun serializeAsValue(value: Pokemon): String = Uri.encode(Json.encodeToString(value)) } diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 0592946..1ff848c 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -18,7 +18,7 @@ plugins { id("skydoves.pokedex.android.library") id("skydoves.pokedex.android.hilt") id("skydoves.pokedex.spotless") - id("kotlinx-serialization") + alias(libs.plugins.kotlinx.serialization) } android { diff --git a/feature/home/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt b/feature/home/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt index 062f993..7ac7b81 100644 --- a/feature/home/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt +++ b/feature/home/src/main/kotlin/com/skydoves/pokedex/compose/feature/home/PokedexHome.kt @@ -69,7 +69,7 @@ import com.skydoves.pokedex.compose.core.designsystem.component.PokedexCircularP import com.skydoves.pokedex.compose.core.designsystem.component.pokedexSharedElement import com.skydoves.pokedex.compose.core.designsystem.theme.PokedexTheme import com.skydoves.pokedex.compose.core.model.Pokemon -import com.skydoves.pokedex.compose.core.navigation.PokedexScreens +import com.skydoves.pokedex.compose.core.navigation.PokedexScreen import com.skydoves.pokedex.compose.core.navigation.boundsTransform import com.skydoves.pokedex.compose.core.navigation.currentComposeNavigator import com.skydoves.pokedex.compose.core.preview.PokedexPreviewTheme @@ -145,7 +145,7 @@ private fun SharedTransitionScope.PokemonCard( .fillMaxWidth() .testTag("Pokemon") .clickable { - composeNavigator.navigate(PokedexScreens.Details.createRoute(pokemon = pokemon)) + composeNavigator.navigate(PokedexScreen.Details(pokemon = pokemon)) }, shape = RoundedCornerShape(14.dp), colors = CardColors( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e2c8a3..ce3dffc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,18 @@ [versions] agp = "8.4.0" -kotlin = "1.9.23" -ksp = "1.9.23-1.0.20" +kotlin = "1.9.24" +ksp = "1.9.24-1.0.20" kotlinxImmutable = "0.3.7" -androidxActivity = "1.8.2" -androidxLifecycle = "2.7.0" +androidxActivity = "1.9.0" +androidxCore = "1.13.1" +androidxLifecycle = "2.8.0" androidxRoom = "2.6.1" androidxArchCore = "2.2.0" androidXStartup = "1.1.1" -androidxCompose = "1.7.0-alpha07" -androidxComposeCompiler = "1.5.11" -androidxComposeMaterial3 = "1.3.0-alpha04" -androidxComposeNavigation = "2.7.7" +androidxCompose = "1.7.0-beta01" +androidxComposeCompiler = "1.5.14" +androidxComposeMaterial3 = "1.3.0-beta01" +androidxNavigation = "2.8.0-beta01" androidxHiltNavigationCompose = "1.2.0" composeStableMarker = "1.0.4" kotlinxSerializationJson = "1.6.3" @@ -20,7 +21,7 @@ retrofit = "2.11.0" okHttp = "4.12.0" sandwich = "2.0.7" landscapist = "2.3.3" -coroutines = "1.8.0" +coroutines = "1.8.1" profileInstaller = "1.3.1" macroBenchmark = "1.2.3" uiAutomator = "2.3.0" @@ -44,7 +45,8 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = androidx-arch-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "androidxArchCore" } androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidXStartup" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } -androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation" } +androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "androidxCompose" } androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "androidxCompose" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxComposeMaterial3" }