diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1a4be0e..791aad7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -160,8 +160,7 @@ dependencies { implementation(Dependencies.Compose.animation) implementation(Dependencies.Compose.foundation) implementation(Dependencies.Compose.foundation_layout) - implementation(Dependencies.Compose.material) - implementation(Dependencies.Compose.material_icons_extended) + implementation(Dependencies.Compose.material3) implementation(Dependencies.Compose.runtime_livedata) implementation(Dependencies.Compose.runtime) implementation(Dependencies.Compose.ui) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index edd0de6..a712bad 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - + + android:supportsRtl="true" + tools:targetApi="tiramisu"> () + sealed class Destination( val route: String, val arguments: List = emptyList(), val deepLinks: List = emptyList(), + val destinationScreen: @Composable (router: NavRouter) -> Unit, ) { - data object Home : Destination(route = "home") + data object Home : Destination( + route = "home", + destinationScreen = { HomeScreen(navigation = it) }, + ) + data object Detail : Destination( route = "detail/{title}?subtitle={subtitle}?value={value}", - arguments = listOf( + destinationScreen = { DetailScreen(navigation = it) }, + arguments = + listOf( navArgument("title") { type = NavType.StringType }, @@ -47,7 +64,7 @@ sealed class Destination( /** * Registers provided [destination] as a composable in [NavGraphBuilder]. */ -fun NavGraphBuilder.composable( +fun NavGraphBuilder.composableScreen( destination: Destination, content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit, ) = composable( diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavRouter.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavRouter.kt new file mode 100644 index 0000000..e77eee0 --- /dev/null +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavRouter.kt @@ -0,0 +1,12 @@ +package app.futured.androidprojecttemplate.navigation + +interface NavRouter { + fun popBackStack() + fun navigateBack(popUpToDestination: Destination, inclusive: Boolean = false) + + fun navigateToDetail(title: String, subtitle: String? = null, value: String? = null) + + fun navigateBackWithResult(key: String, value: T) + fun setCurrentResult(key: String, value: T) + fun subscribeForResult(key: String, callback: (T) -> Unit) +} diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavRouterImpl.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavRouterImpl.kt new file mode 100644 index 0000000..18f66e4 --- /dev/null +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavRouterImpl.kt @@ -0,0 +1,54 @@ +package app.futured.androidprojecttemplate.navigation + +import androidx.navigation.NavController +import app.futured.androidprojecttemplate.tools.extensions.subscribeForResult +import timber.log.Timber + +/** + * Class that triggers navigation actions on provided [navController]. + */ +class NavRouterImpl(private val navController: NavController) : NavRouter { + override fun popBackStack() { + navController.navigateUp() + } + + override fun navigateBack(popUpToDestination: Destination, inclusive: Boolean) { + navController.popBackStack(route = popUpToDestination.route, inclusive = inclusive) + } + + override fun navigateToDetail(title: String, subtitle: String?, value: String?) = + Destination.Detail.buildRoute(title, subtitle, value).execute() + + override fun navigateBackWithResult(key: String, value: T) { + navController.previousBackStackEntry?.savedStateHandle?.also { + it[key] = value + navController.popBackStack() + } + } + + override fun setCurrentResult(key: String, value: T) { + navController.currentBackStackEntry?.savedStateHandle?.also { + it[key] = value + } + } + + override fun subscribeForResult(key: String, callback: (T) -> Unit) { + navController.currentBackStackEntry?.savedStateHandle?.subscribeForResult(key) { callback(it) } + } + + private fun String.execute( + popUpToDestinationRoute: String? = null, + isInclusive: Boolean = true, + ) { + Timber.d("## Navigate to $this, popupTo $popUpToDestinationRoute, inclusive $isInclusive") + if (popUpToDestinationRoute != null) { + navController.navigate(this) { + popUpTo(popUpToDestinationRoute) { + inclusive = isInclusive + } + } + } else { + navController.navigate(this) + } + } +} diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavigationDestinations.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavigationDestinations.kt deleted file mode 100644 index 40e2800..0000000 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/navigation/NavigationDestinations.kt +++ /dev/null @@ -1,21 +0,0 @@ -package app.futured.androidprojecttemplate.navigation - -import androidx.navigation.NavController - -interface NavigationDestinations { - fun popBackStack() - fun navigateToDetailScreen(title: String, subtitle: String? = null, value: String? = null) -} - -/** - * Class that triggers navigation actions on provided [navController]. - */ -class NavigationDestinationsImpl(private val navController: NavController) : NavigationDestinations { - - override fun popBackStack() { - navController.popBackStack() - } - - override fun navigateToDetailScreen(title: String, subtitle: String?, value: String?) = - navController.navigate(Destination.Detail.buildRoute(title, subtitle, value)) -} diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/tools/extensions/GeneralExtensions.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/tools/extensions/GeneralExtensions.kt new file mode 100644 index 0000000..2539ce6 --- /dev/null +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/tools/extensions/GeneralExtensions.kt @@ -0,0 +1,28 @@ +package app.futured.androidprojecttemplate.tools.extensions + +inline fun T?.ifNull(defaultValue: () -> T): T = this ?: defaultValue.invoke() + +inline fun withValue(receiver: T, block: T.() -> Unit) { + receiver.block() +} + +inline fun withNonNullValue(receiver: T?, block: (T) -> Unit) { + if (receiver != null) block(receiver) +} + +inline fun safe(block: () -> T): T? { + return try { + block() + } catch (e: Exception) { + e.printStackTrace() + null + } +} + +fun T?.orThrow(): T = this ?: error("UnexpectedError") // Dev error + +inline fun ifElseNull(predicate: Boolean, block: () -> T?) = if (predicate) block.invoke() else null + +inline fun T.runWith(block: (T) -> Unit) { + block(this) +} diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/tools/extensions/SavedStateHandleExtensions.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/tools/extensions/SavedStateHandleExtensions.kt new file mode 100644 index 0000000..2203b9f --- /dev/null +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/tools/extensions/SavedStateHandleExtensions.kt @@ -0,0 +1,44 @@ +package app.futured.androidprojecttemplate.tools.extensions + +import android.util.Base64.NO_PADDING +import android.util.Base64.NO_WRAP +import android.util.Base64.URL_SAFE +import android.util.Base64.decode +import android.util.Base64.encodeToString +import androidx.lifecycle.Observer +import androidx.lifecycle.SavedStateHandle +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +inline fun SavedStateHandle.getSerializedArgument(key: String): T? = get(key)?.deserializeFromNavArgument() + +inline fun SavedStateHandle.getRequiredSerializedArgument(key: String): T = + getSerializedArgument(key) ?: error("Required parameter is not present") + +inline fun SavedStateHandle.getArgument(key: String): T? = safe { get(key) } + +inline fun SavedStateHandle.getRequiredArgument(key: String): T = + getArgument(key) ?: getSerializedArgument(key) ?: error("Required parameter is not present") + +inline fun T.serializeAsNavArgument(): String? = + encodeToString(Json.encodeToString(this).encodeToByteArray(), NO_PADDING or NO_WRAP or URL_SAFE) + +inline fun String.deserializeFromNavArgument(): T? = + safe { Json.decodeFromString(decode(this, NO_PADDING or NO_WRAP or URL_SAFE).decodeToString()) } + +fun SavedStateHandle.subscribeForResult(key: String, callback: (T) -> Unit) { + val liveData = this.getLiveData(key) + val observer = object : Observer { + override fun onChanged(t: T) { + if (t != null) { + val value = remove(key) + if (value != null) { + callback(t) + } + liveData.removeObserver(this) + subscribeForResult(key, callback) + } + } + } + liveData.observeForever(observer) +} diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/NavGraph.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/NavGraph.kt index 1bc3194..52c9542 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/NavGraph.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/NavGraph.kt @@ -1,32 +1,40 @@ package app.futured.androidprojecttemplate.ui +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import app.futured.androidprojecttemplate.navigation.Destination -import app.futured.androidprojecttemplate.navigation.NavigationDestinations -import app.futured.androidprojecttemplate.navigation.NavigationDestinationsImpl -import app.futured.androidprojecttemplate.navigation.composable -import app.futured.androidprojecttemplate.ui.screens.detail.DetailScreen -import app.futured.androidprojecttemplate.ui.screens.home.HomeScreen +import app.futured.androidprojecttemplate.navigation.NavRouter +import app.futured.androidprojecttemplate.navigation.NavRouterImpl +import app.futured.androidprojecttemplate.navigation.composableDialog +import app.futured.androidprojecttemplate.navigation.composableScreen +import app.futured.androidprojecttemplate.navigation.dialogs +import app.futured.androidprojecttemplate.navigation.screens @Composable fun NavGraph( navController: NavHostController = rememberNavController(), - navigation: NavigationDestinations = remember { NavigationDestinationsImpl(navController) }, + navigation: NavRouter = remember { NavRouterImpl(navController) }, ) { + LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher?.let { + navController.navigateUp() + } + NavHost( navController = navController, startDestination = Destination.Home.route, ) { - composable(Destination.Home) { - HomeScreen(navigation) + // Destinations without navbar at the bottom + screens.forEach { destination -> + composableScreen(destination) { destination.destinationScreen(navigation) } } - composable(Destination.Detail) { - DetailScreen(navigation) + // Dialogs + dialogs.forEach { destination -> + composableDialog(destination) { destination.destinationScreen(navigation) } } } } diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/AddFloatingActionButton.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/AddFloatingActionButton.kt index b63d087..160f996 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/AddFloatingActionButton.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/AddFloatingActionButton.kt @@ -1,9 +1,9 @@ package app.futured.androidprojecttemplate.ui.components -import androidx.compose.material.FloatingActionButton -import androidx.compose.material.Icon import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import app.futured.androidprojecttemplate.tools.compose.ComponentPreviews diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/Showcase.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/Showcase.kt index 20c39d3..c8fb836 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/Showcase.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/components/Showcase.kt @@ -1,7 +1,7 @@ package app.futured.androidprojecttemplate.ui.components -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import app.futured.androidprojecttemplate.ui.theme.AppTheme @@ -12,7 +12,7 @@ import app.futured.androidprojecttemplate.ui.theme.AppTheme @Composable fun Showcase(modifier: Modifier = Modifier, content: @Composable () -> Unit) { AppTheme { - Surface(color = MaterialTheme.colors.background, modifier = modifier) { + Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) { content() } } diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/_templateScreen/_TEMPLATEScreen.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/_templateScreen/_TEMPLATEScreen.kt new file mode 100644 index 0000000..e3bdd1a --- /dev/null +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/_templateScreen/_TEMPLATEScreen.kt @@ -0,0 +1,123 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package app.futured.androidprojecttemplate.ui.screens._templateScreen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import app.futured.androidprojecttemplate.navigation.NavRouter +import app.futured.androidprojecttemplate.tools.arch.BaseViewModel +import app.futured.androidprojecttemplate.tools.arch.EventsEffect +import app.futured.androidprojecttemplate.tools.arch.onEvent +import app.futured.androidprojecttemplate.tools.compose.ScreenPreviews +import app.futured.androidprojecttemplate.ui.components.Showcase +import app.futured.arkitekt.core.ViewState +import app.futured.arkitekt.core.event.Event +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Inject + +/** + * This is a template for creating new screens: + * + * 1. Copy wherever you wanna create the screen. + * 2. Select all occurrences of "TEMPLATE" (Ctrl + G) and rename to your liking. + * 3. Extract all parts to respective files (alt+enter on interface/class signature -> extract from file). + */ +@Composable +fun TEMPLATEScreen( + navigation: NavRouter, + viewModel: TEMPLATEViewModel = hiltViewModel(), +) { + with(viewModel) { + EventsEffect { + onEvent { + navigation.popBackStack() + } + } + + TEMPLATE.Content( + actions = this, + ) + } +} + +object TEMPLATE { + @Stable + interface Actions { + fun onNavigateBack() + } + + @Composable + fun Content( + actions: Actions, + modifier: Modifier = Modifier, + ) { + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = "TEMPLATEScreen") }, + navigationIcon = { + IconButton( + onClick = { actions.onNavigateBack() }, + ) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, "") + } + }, + ) + }, + modifier = modifier, + ) { contentPadding -> + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(contentPadding), + ) { + Text(text = "TEMPLATE") + } + } + } +} + +@ScreenPreviews +@Composable +fun TEMPLATEContentPreview() = Showcase { + TEMPLATE.Content( + actions = + object : TEMPLATE.Actions { + override fun onNavigateBack() = Unit + }, + ) +} + +sealed class TEMPLATEEvent : Event() + +data object NavigateBackEvent : TEMPLATEEvent() + +@HiltViewModel +class TEMPLATEViewModel @Inject constructor( + override val viewState: TEMPLATEViewState, +) : BaseViewModel(), TEMPLATE.Actions { + override fun onNavigateBack() { + sendEvent(NavigateBackEvent) + } +} + +@ViewModelScoped +class TEMPLATEViewState @Inject constructor() : ViewState diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailScreen.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailScreen.kt index 47b78ef..89cefc3 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailScreen.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailScreen.kt @@ -1,22 +1,24 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package app.futured.androidprojecttemplate.ui.screens.detail -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel -import app.futured.androidprojecttemplate.navigation.NavigationDestinations +import app.futured.androidprojecttemplate.navigation.NavRouter import app.futured.androidprojecttemplate.tools.arch.EventsEffect import app.futured.androidprojecttemplate.tools.arch.onEvent import app.futured.androidprojecttemplate.tools.compose.ScreenPreviews @@ -25,7 +27,7 @@ import app.futured.androidprojecttemplate.ui.components.Showcase @Composable fun DetailScreen( - navigation: NavigationDestinations, + navigation: NavRouter, viewModel: DetailViewModel = hiltViewModel(), ) { with(viewModel) { @@ -43,15 +45,12 @@ fun DetailScreen( } object Detail { - interface Actions { - fun navigateBack() = Unit - fun incrementCounter() = Unit - } + fun onNavigateBack() - object PreviewActions : Actions + fun onIncrementCounter() + } - @SuppressLint("ComposeModifierMissing") @Composable fun Content( actions: Actions, @@ -64,9 +63,9 @@ object Detail { title = { Text(text = "DetailScreen") }, navigationIcon = { IconButton( - onClick = { actions.navigateBack() }, + onClick = { actions.onNavigateBack() }, ) { - Icon(Icons.Default.ArrowBack, "") + Icon(Icons.AutoMirrored.Filled.ArrowBack, "") } }, ) @@ -74,7 +73,7 @@ object Detail { floatingActionButton = { AddFloatingActionButton( onClick = { - actions.incrementCounter() + actions.onIncrementCounter() }, ) }, @@ -84,8 +83,8 @@ object Detail { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .padding(contentPadding) - .fillMaxSize(), + .fillMaxSize() + .padding(contentPadding), ) { Text(text = "Detail: $counter") } @@ -98,7 +97,10 @@ object Detail { fun DetailContentPreview() { Showcase { Detail.Content( - Detail.PreviewActions, + actions = object : Detail.Actions { + override fun onNavigateBack() = Unit + override fun onIncrementCounter() = Unit + }, counter = 5, ) } diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailViewModel.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailViewModel.kt index a243d72..15aa24c 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailViewModel.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/detail/DetailViewModel.kt @@ -8,12 +8,11 @@ import javax.inject.Inject class DetailViewModel @Inject constructor( override val viewState: DetailViewState, ) : BaseViewModel(), Detail.Actions { - - override fun navigateBack() { + override fun onNavigateBack() { sendEvent(NavigateBackEvent) } - override fun incrementCounter() { + override fun onIncrementCounter() { viewState.counter++ } } diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeScreen.kt index 95123d7..134041c 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeScreen.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package app.futured.androidprojecttemplate.ui.screens.home import androidx.compose.foundation.clickable @@ -5,14 +7,16 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel -import app.futured.androidprojecttemplate.navigation.NavigationDestinations +import app.futured.androidprojecttemplate.navigation.NavRouter import app.futured.androidprojecttemplate.tools.arch.EventsEffect import app.futured.androidprojecttemplate.tools.arch.onEvent import app.futured.androidprojecttemplate.tools.compose.ScreenPreviews @@ -21,13 +25,13 @@ import app.futured.androidprojecttemplate.ui.components.Showcase @Composable fun HomeScreen( - navigation: NavigationDestinations, + navigation: NavRouter, viewModel: HomeViewModel = hiltViewModel(), ) { with(viewModel) { EventsEffect { onEvent { - navigation.navigateToDetailScreen( + navigation.navigateToDetail( title = "Demo", subtitle = "Subtitle", value = "Demo Subtitle", @@ -44,15 +48,13 @@ fun HomeScreen( object Home { + @Stable interface Actions { + fun onNavigateToDetail() - fun navigateToDetailScreen() = Unit - - fun incrementCounter() = Unit + fun onIncrementCounter() } - object PreviewActions : Actions - @Composable fun Content( actions: Actions, @@ -64,7 +66,7 @@ object Home { floatingActionButton = { AddFloatingActionButton( onClick = { - actions.incrementCounter() + actions.onIncrementCounter() }, ) }, @@ -74,10 +76,10 @@ object Home { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .padding(contentPadding) .fillMaxSize() + .padding(contentPadding) .clickable { - actions.navigateToDetailScreen() + actions.onNavigateToDetail() }, ) { Text(text = "Home: $counter") @@ -91,7 +93,11 @@ object Home { fun HomeContentPreview() { Showcase { Home.Content( - Home.PreviewActions, + actions = + object : Home.Actions { + override fun onNavigateToDetail() = Unit + override fun onIncrementCounter() = Unit + }, counter = 5, ) } diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeViewModel.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeViewModel.kt index c6a77bb..4dea20e 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/screens/home/HomeViewModel.kt @@ -8,12 +8,11 @@ import javax.inject.Inject class HomeViewModel @Inject constructor( override val viewState: HomeViewState, ) : BaseViewModel(), Home.Actions { - - override fun incrementCounter() { + override fun onIncrementCounter() { viewState.counter++ } - override fun navigateToDetailScreen() { + override fun onNavigateToDetail() { sendEvent(NavigateToDetailEvent) } } diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Dimensions.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Dimensions.kt new file mode 100644 index 0000000..c3c40a0 --- /dev/null +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Dimensions.kt @@ -0,0 +1,118 @@ +@file:Suppress("MagicNumber") + +package app.futured.androidprojecttemplate.ui.theme + +import androidx.compose.ui.unit.dp + +object Grid { + val d0 = 0.dp + val d0_25 = 1.dp + val d0_375 = 1.5.dp + val d0_5 = 2.dp + val d0_75 = 3.dp + val d1 = 4.dp + val d1_25 = 5.dp + val d1_5 = 6.dp + val d2 = 8.dp + val d2_25 = 9.dp + val d2_5 = 10.dp + val d3 = 12.dp + val d3_5 = 14.dp + val d4 = 16.dp + val d5 = 20.dp + val d6 = 24.dp + val d7 = 28.dp + val d8 = 32.dp + val d9 = 36.dp + val d10 = 40.dp + val d11 = 44.dp + val d12 = 48.dp + val d13 = 52.dp + val d14 = 56.dp + val d15 = 60.dp + val d16 = 64.dp + val d17 = 68.dp + val d18 = 72.dp + val d19 = 76.dp + val d20 = 80.dp + val d21 = 84.dp + val d22 = 88.dp + val d23 = 92.dp + val d24 = 96.dp + val d25 = 100.dp + val d26 = 104.dp + val d27 = 108.dp + val d28 = 112.dp + val d29 = 116.dp + val d30 = 120.dp + val d31 = 124.dp + val d32 = 128.dp + val d33 = 132.dp + val d34 = 136.dp + val d35 = 140.dp + val d36 = 144.dp + val d37 = 148.dp + val d38 = 152.dp + val d39 = 156.dp + val d40 = 160.dp + val d41 = 164.dp + val d42 = 168.dp + val d43 = 172.dp + val d44 = 176.dp + val d45 = 180.dp + val d46 = 184.dp + val d47 = 188.dp + val d48 = 192.dp + val d49 = 196.dp + val d50 = 200.dp + val d51 = 204.dp + val d52 = 208.dp + val d53 = 212.dp + val d54 = 216.dp + val d55 = 220.dp + val d56 = 224.dp + val d57 = 228.dp + val d58 = 232.dp + val d59 = 236.dp + val d60 = 240.dp + val d61 = 244.dp + val d62 = 248.dp + val d63 = 252.dp + val d64 = 256.dp + val d65 = 260.dp + val d66 = 264.dp + val d67 = 268.dp + val d68 = 272.dp + val d69 = 276.dp + val d70 = 280.dp + val d71 = 284.dp + val d72 = 288.dp + val d73 = 292.dp + val d74 = 296.dp + val d75 = 300.dp + val d76 = 304.dp + val d77 = 308.dp + val d78 = 312.dp + val d79 = 316.dp + val d80 = 320.dp + val d81 = 324.dp + val d82 = 328.dp + val d83 = 332.dp + val d84 = 336.dp + val d85 = 340.dp + val d86 = 344.dp + val d87 = 348.dp + val d88 = 352.dp + val d89 = 356.dp + val d90 = 360.dp + val d91 = 364.dp + val d92 = 368.dp + val d93 = 372.dp + val d94 = 376.dp + val d95 = 380.dp + val d96 = 384.dp + val d97 = 388.dp + val d98 = 392.dp + val d99 = 396.dp + val d100 = 400.dp +} diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Shape.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Shape.kt index 08c6a1b..5c56840 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Shape.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Shape.kt @@ -3,7 +3,7 @@ package app.futured.androidprojecttemplate.ui.theme import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes +import androidx.compose.material3.Shapes import androidx.compose.ui.unit.dp val shapes = Shapes( diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Theme.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Theme.kt index e802ac0..dbdcea4 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Theme.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Theme.kt @@ -1,28 +1,29 @@ package app.futured.androidprojecttemplate.ui.theme import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable -private val LightColorPalette = lightColors( +private val LightColorPalette = lightColorScheme( primary = orange300, - primaryVariant = orange100, + primaryContainer = orange100, secondary = blue400, - secondaryVariant = blue200, + secondaryContainer = blue200, background = cloud50, - surface = pureWhite, + surface = orange100, onPrimary = pureWhite, onSecondary = pureWhite, onBackground = black900, onSurface = black900, ) -private val DarkColorPalette = darkColors( +private val DarkColorPalette = darkColorScheme( primary = orange300, - primaryVariant = orange100, + primaryContainer = orange300, secondary = blue400, + secondaryContainer = blue200, background = black900, surface = black700, onPrimary = pureWhite, @@ -43,7 +44,7 @@ fun AppTheme( } MaterialTheme( - colors = colors, + colorScheme = colors, typography = typography, shapes = shapes, content = content, diff --git a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Type.kt b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Type.kt index abb5d23..3f00486 100644 --- a/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Type.kt +++ b/app/src/main/kotlin/app/futured/androidprojecttemplate/ui/theme/Type.kt @@ -1,13 +1,13 @@ package app.futured.androidprojecttemplate.ui.theme -import androidx.compose.material.Typography +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp val typography = Typography( - body1 = TextStyle( + bodyMedium = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp, diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 2229a35..0ee66a6 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -16,8 +16,7 @@ object Dependencies { const val animation = "androidx.compose.animation:animation:${Versions.composeVersion}" const val foundation = "androidx.compose.foundation:foundation:${Versions.composeFoundationVersion}" const val foundation_layout = "androidx.compose.foundation:foundation-layout:${Versions.composeFoundationVersion}" - const val material = "androidx.compose.material:material:${Versions.composeMaterialVersion}" - const val material_icons_extended = "androidx.compose.material:material-icons-extended:${Versions.composeMaterialVersion}" + const val material3 = "androidx.compose.material3:material3:${Versions.composeMaterial3Version}" const val runtime_livedata = "androidx.compose.runtime:runtime-livedata:${Versions.composeVersion}" const val runtime = "androidx.compose.runtime:runtime:${Versions.composeVersion}" const val ui = "androidx.compose.ui:ui:${Versions.composeVersion}" diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 1bf55cb..2bacd6d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -6,16 +6,16 @@ object Versions { const val androidGradlePlugin = "8.4.0" // plugins - const val detekt = "1.23.6" - const val ktlintGradle = "12.1.0" - const val ktlint = "1.0.1" + const val detekt = "1.22.0" + const val ktlintGradle = "11.2.0" + const val ktlint = "0.48.2" // kotlin const val kotlin = "1.9.23" const val composeVersion = "1.6.7" const val composeFoundationVersion = "1.6.7" - const val composeMaterialVersion = "1.6.7" + const val composeMaterial3Version = "1.2.1" const val composeCompilerVersion = "1.5.13" // support @@ -30,7 +30,7 @@ object Versions { const val desugarLibs = "2.0.4" // navigation components - const val navigation = "2.7.7" + const val navigation = "2.8.0-alpha08" const val hiltNavigation = "1.2.0" // networking