From 5047c8412daea3d8134ba7ce34b1283126a309e1 Mon Sep 17 00:00:00 2001 From: Francis Pepin Date: Mon, 6 Nov 2023 17:04:40 -0500 Subject: [PATCH] Add navigation and UI on Android --- .../app/resources/AndroidImageProvider.kt | 1 + .../kmp/boilerplate/app/ui/common/Utils.kt | 5 +- .../app/ui/navigation/BoilerplateNavHost.kt | 46 ++++++ .../app/ui/navigation/NavigationView.kt | 27 ++++ .../app/ui/navigation/VMDNavigableContent.kt | 26 +++ .../app/ui/navigation/VMDNavigationView.kt | 108 +++++++++++++ .../ProjectDetailsContentView.kt | 148 ++++++++++++++++++ .../ui/projectdetails/ProjectDetailsView.kt | 97 ++++++++++++ .../app/ui/projects/ProjectsContentView.kt | 11 ++ .../app/ui/projects/ProjectsView.kt | 17 +- .../kmp/boilerplate/app/ui/theme/Colors.kt | 2 +- .../main/res/drawable/baseline_close_24.xml | 5 + 12 files changed, 484 insertions(+), 9 deletions(-) create mode 100644 androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/BoilerplateNavHost.kt create mode 100644 androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/NavigationView.kt create mode 100644 androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigableContent.kt create mode 100644 androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigationView.kt create mode 100644 androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsContentView.kt create mode 100644 androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsView.kt create mode 100644 androidApp/src/main/res/drawable/baseline_close_24.xml diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/resources/AndroidImageProvider.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/resources/AndroidImageProvider.kt index 75f6101..9cce358 100644 --- a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/resources/AndroidImageProvider.kt +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/resources/AndroidImageProvider.kt @@ -12,6 +12,7 @@ class AndroidImageProvider : VMDImageProvider { SharedImageResource.emptyPageIcon -> R.drawable.baseline_question_mark_24 SharedImageResource.errorPageIcon -> R.drawable.baseline_warning_24 SharedImageResource.imagePlaceholder -> R.drawable.baseline_image_24 + SharedImageResource.closeIcon -> R.drawable.baseline_close_24 } else -> null } diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/common/Utils.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/common/Utils.kt index 9dfa37e..12d1192 100644 --- a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/common/Utils.kt +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/common/Utils.kt @@ -7,6 +7,7 @@ import com.google.accompanist.placeholder.placeholder import com.google.accompanist.placeholder.shimmer import com.mirego.kmp.boilerplate.app.ui.theme.ShimmerBackground import com.mirego.kmp.boilerplate.app.ui.theme.ShimmerHighlight +import com.mirego.trikot.viewmodels.declarative.properties.VMDColor fun Modifier.loading(isLoading: Boolean) = this.then( placeholder( @@ -14,4 +15,6 @@ fun Modifier.loading(isLoading: Boolean) = this.then( highlight = PlaceholderHighlight.shimmer(highlightColor = Color.ShimmerHighlight), color = Color.ShimmerBackground ) -) \ No newline at end of file +) + +fun VMDColor.toColor() = Color(red, green, blue) \ No newline at end of file diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/BoilerplateNavHost.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/BoilerplateNavHost.kt new file mode 100644 index 0000000..b2611b3 --- /dev/null +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/BoilerplateNavHost.kt @@ -0,0 +1,46 @@ +package com.mirego.kmp.boilerplate.app.ui.navigation + +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.mirego.kmp.boilerplate.app.ui.projectdetails.ProjectDetailsView +import com.mirego.kmp.boilerplate.viewmodel.navigation.NavigationRoute +import com.mirego.kmp.boilerplate.viewmodel.navigation.NavigationViewModel + +@Composable +fun BoilerplateNavHost( + navController: NavHostController, + startDestination: String, + navigationViewModel: NavigationViewModel, + content: @Composable () -> Unit +) = NavHost( + navController = navController, + startDestination = startDestination, + enterTransition = { fadeIn(tween(500)) }, + exitTransition = { fadeOut(tween(500)) } +) { + composable(startDestination) { + content() + } + composable(NavigationRoute.ProjectDetails.NAME) { + NavigableContent(navigationViewModel) + } +} + +@Composable +private fun NavigableContent(navigationViewModel: NavigationViewModel) { + Box(modifier = Modifier.fillMaxSize()) { + VMDNavigableContent(navigationViewModel = navigationViewModel) { navigationRoute -> + when (navigationRoute) { + is NavigationRoute.ProjectDetails -> ProjectDetailsView(projectDetailsViewModel = navigationRoute.viewModel) + } + } + } +} diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/NavigationView.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/NavigationView.kt new file mode 100644 index 0000000..4406b9b --- /dev/null +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/NavigationView.kt @@ -0,0 +1,27 @@ +package com.mirego.kmp.boilerplate.app.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.navigation.compose.rememberNavController +import com.mirego.kmp.boilerplate.viewmodel.navigation.NavigationViewModel + +@Composable +fun NavigationView(navigationViewModel: NavigationViewModel, content: @Composable () -> Unit) { + if (LocalInspectionMode.current) { + content() + return + } + + val navController = rememberNavController() + VMDNavigationView( + navigationViewModel = navigationViewModel, + navController = navController + ) { startDestination -> + BoilerplateNavHost( + navController = navController, + startDestination = startDestination, + navigationViewModel = navigationViewModel, + content = content + ) + } +} diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigableContent.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigableContent.kt new file mode 100644 index 0000000..38eb05c --- /dev/null +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigableContent.kt @@ -0,0 +1,26 @@ +package com.mirego.kmp.boilerplate.app.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.mirego.kmp.mirego.trikot.viewmodels.declarative.navigation.VMDNavigationRoute +import com.mirego.kmp.mirego.trikot.viewmodels.declarative.navigation.VMDNavigationViewModel + +/** + * When using VMDNavigationView, wrap the different navigation destinations in a VMDNavigableContent + * + * @param navigationViewModel The VMDNavigationViewModel + * @param content The wrapped navigation content. The block will receive the navigationRoute, as well as a flag to indicate if the composition is done in the context of an exit transition. + */ +@Composable +fun VMDNavigableContent( + navigationViewModel: VMDNavigationViewModel, + content: @Composable (navigationRoute: T) -> Unit +) { + val route: T? by remember(key1 = navigationViewModel) { + mutableStateOf(navigationViewModel.navigationRoute) + } + val navigationRoute = route ?: return + content(navigationRoute) +} diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigationView.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigationView.kt new file mode 100644 index 0000000..6541326 --- /dev/null +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/navigation/VMDNavigationView.kt @@ -0,0 +1,108 @@ +package com.mirego.kmp.boilerplate.app.ui.navigation + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.navigation.NavDestination +import androidx.navigation.NavHostController +import androidx.navigation.NavOptionsBuilder +import com.mirego.kmp.boilerplate.viewmodel.navigation.NavigationViewModel +import com.mirego.kmp.mirego.trikot.viewmodels.declarative.navigation.VMDNavigationRoute +import com.mirego.kmp.mirego.trikot.viewmodels.declarative.navigation.VMDNavigationViewModel +import com.mirego.trikot.viewmodels.declarative.compose.extensions.observeAsState +import kotlinx.coroutines.flow.MutableStateFlow + +private const val ROOT_ROUTE = "ROOT" + +/** + * For views backed by a VMDNavigationViewModel, wrap your content with VMDNavigationView to handle navigation. + * + * @param navigationViewModel The VMDNavigationViewModel + * @param navController The NavHostController that controls the navigation. Note that it is possible to use custom implementations (for instance if a navigation library is used). + * @param navOptionsBuilder The options to use for navigation + * @param navHost The navHost containing the different navigation destinations. Note that it is possible to use custom implementations (for instance if a navigation library is used). + */ +@Composable +fun VMDNavigationView( + navigationViewModel: VMDNavigationViewModel, + navController: NavHostController, + navOptionsBuilder: NavOptionsBuilder.() -> Unit = { + launchSingleTop = true + }, + navHost: @Composable (startDestination: String) -> Unit +) { + navHost(startDestination = ROOT_ROUTE) + + NavigationHandler( + navigationViewModel = navigationViewModel, + navController = navController, + navOptionsBuilder = navOptionsBuilder + ) +} + +@Composable +private fun NavigationHandler( + navigationViewModel: VMDNavigationViewModel, + navController: NavHostController, + navOptionsBuilder: NavOptionsBuilder.() -> Unit +) { + val navigationRoute by navigationViewModel.observeAsState(navigationViewModel::navigationRoute, navigationViewModel.navigationRoute) + val currentRoute = navController.currentDestination + + when (val navigation = getNavigation(navigationRoute, currentRoute)) { + is VMDNavigation.POP -> navController.popBackStack() + is VMDNavigation.LAUNCH -> navController.navigate( + route = navigation.destinationRoute, + builder = navOptionsBuilder + ) + is VMDNavigation.NONE -> Unit + } + val lifecycleEventState = MutableStateFlow(Lifecycle.Event.ON_START) + val observer = LifecycleEventObserver { _, event -> + lifecycleEventState.value = event + } + val lifecycleOwner = LocalLifecycleOwner.current + + lifecycleOwner.lifecycle.addObserver(observer) + + DisposableEffect(lifecycleOwner) { + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + val state = lifecycleEventState.collectAsState() + + key(state.value) { + BackHandler( + enabled = navigationRoute != null && (navigationRoute?.viewModel as? NavigationViewModel)?.navigationRoute == null + ) { + navigationRoute?.resetBlock?.invoke() + } + } +} + +private fun getNavigation( + navigationRoute: VMDNavigationRoute?, + currentNavDestination: NavDestination? +): VMDNavigation = when { + navigationRoute == null && !currentNavDestination.isRoot -> VMDNavigation.POP + navigationRoute != null && currentNavDestination.isRoot -> VMDNavigation.LAUNCH(navigationRoute.name) + else -> VMDNavigation.NONE +} + +private val NavDestination?.isRoot: Boolean + get() = this == null || this.route == null || this.route == ROOT_ROUTE + +private sealed class VMDNavigation { + object NONE : VMDNavigation() + object POP : VMDNavigation() + data class LAUNCH( + val destinationRoute: String + ) : VMDNavigation() +} diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsContentView.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsContentView.kt new file mode 100644 index 0000000..9ec89da --- /dev/null +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsContentView.kt @@ -0,0 +1,148 @@ +package com.mirego.kmp.boilerplate.app.ui.projectdetails + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import com.mirego.kmp.boilerplate.app.ui.common.Const.padding +import com.mirego.kmp.boilerplate.app.ui.common.loading +import com.mirego.kmp.boilerplate.app.ui.common.toColor +import com.mirego.kmp.boilerplate.app.ui.theme.TextSize +import com.mirego.kmp.boilerplate.app.ui.theme.TextWeight +import com.mirego.kmp.boilerplate.app.ui.theme.style +import com.mirego.kmp.boilerplate.viewmodel.projectdetails.ProjectDetailsRoot +import com.mirego.trikot.viewmodels.declarative.compose.viewmodel.LocalImage +import com.mirego.trikot.viewmodels.declarative.compose.viewmodel.PlaceholderState +import com.mirego.trikot.viewmodels.declarative.compose.viewmodel.VMDImage +import com.mirego.trikot.viewmodels.declarative.properties.VMDImageResource + +@Composable +fun ProjectDetailsContentView(content: ProjectDetailsRoot.Content) { + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp.dp + val textColor = content.textColor.toColor() + Box( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + ) { + VMDImage( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = screenWidth * 1.25f) + .align(Alignment.TopCenter), + viewModel = content.image, + contentScale = ContentScale.FillWidth, + placeholder = { placeholderImageResource: VMDImageResource, _: PlaceholderState -> + ImagePlaceholder( + placeholderImageResource = placeholderImageResource, + color = textColor + ) + } + ) + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(horizontal = padding) + .padding(bottom = padding), + ) { + Text( + modifier = Modifier.loading(content.isLoading), + text = content.title, + style = style(TextSize.LARGE_TITLE, TextWeight.BOLD), + color = textColor + ) + + Text( + modifier = Modifier + .padding(top = 24.dp) + .loading(content.isLoading), + text = content.subtitle, + style = style(TextSize.TITLE1, TextWeight.SEMI_BOLD), + color = textColor + ) + + Box( + modifier = Modifier + .padding(top = 24.dp) + .height(1.dp) + .fillMaxWidth() + .background(textColor) + ) + + Column( + modifier = Modifier.padding(top = 32.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + modifier = Modifier.loading(content.isLoading), + text = content.projectType.first, + style = style(TextSize.SUB_HEADLINE, TextWeight.SEMI_BOLD), + color = textColor + ) + + Text( + modifier = Modifier.loading(content.isLoading), + text = content.projectType.second, + style = style(TextSize.SUB_HEADLINE, TextWeight.REGULAR), + color = textColor, + maxLines = 2 + ) + } + + Column( + modifier = Modifier.padding(top = 16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + modifier = Modifier.loading(content.isLoading), + text = content.releaseYear.first, + style = style(TextSize.SUB_HEADLINE, TextWeight.SEMI_BOLD), + color = textColor + ) + + Text( + modifier = Modifier.loading(content.isLoading), + text = content.releaseYear.second, + style = style(TextSize.SUB_HEADLINE, TextWeight.REGULAR), + color = textColor, + maxLines = 2 + ) + } + } + } +} + +@Composable +private fun ImagePlaceholder(placeholderImageResource: VMDImageResource, color: Color) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + contentAlignment = Alignment.Center + ) { + LocalImage( + modifier = Modifier.size(96.dp), + imageResource = placeholderImageResource, + contentScale = ContentScale.FillWidth, + colorFilter = ColorFilter.tint(color) + ) + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsView.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsView.kt new file mode 100644 index 0000000..aacee3d --- /dev/null +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projectdetails/ProjectDetailsView.kt @@ -0,0 +1,97 @@ +package com.mirego.kmp.boilerplate.app.ui.projectdetails + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.accompanist.systemuicontroller.SystemUiController +import com.mirego.kmp.boilerplate.app.ui.common.ErrorView +import com.mirego.kmp.boilerplate.app.ui.common.toColor +import com.mirego.kmp.boilerplate.app.ui.preview.PreviewProvider +import com.mirego.kmp.boilerplate.usecase.preview.PreviewState +import com.mirego.kmp.boilerplate.viewmodel.projectdetails.ProjectDetailsRoot +import com.mirego.kmp.boilerplate.viewmodel.projectdetails.ProjectDetailsViewModel +import com.mirego.trikot.viewmodels.declarative.compose.extensions.observeAsState +import com.mirego.trikot.viewmodels.declarative.compose.viewmodel.LocalImage +import com.mirego.trikot.viewmodels.declarative.compose.viewmodel.VMDButton + +@Composable +fun ProjectDetailsView(projectDetailsViewModel: ProjectDetailsViewModel, systemUiController: SystemUiController? = null) { + val viewModel: ProjectDetailsViewModel by projectDetailsViewModel.observeAsState() + systemUiController?.setNavigationBarColor(color = viewModel.backgroundColor.toColor(), darkIcons = false) + Box( + modifier = Modifier + .fillMaxSize() + .background(viewModel.backgroundColor.toColor()), + ) { + ContentView(viewModel = viewModel) + + VMDButton( + modifier = Modifier + .statusBarsPadding() + .padding(8.dp) + .clip(CircleShape) + .size(40.dp) + .background(viewModel.textColor.toColor().copy(alpha = 0.1f)), + viewModel = viewModel.closeButton + ) { content -> + LocalImage( + modifier = Modifier.size(32.dp), + imageResource = content.image, + colorFilter = ColorFilter.tint(viewModel.textColor.toColor()) + ) + } + } +} + +@Composable +private fun ContentView(viewModel: ProjectDetailsViewModel) { + viewModel.rootContent?.let { content -> + when(content) { + is ProjectDetailsRoot.Content -> ProjectDetailsContentView(content = content) + is ProjectDetailsRoot.Error -> ErrorView(errorViewModel = content.errorViewModel) + } + } +} + +@Preview +@Composable +fun PreviewProjectsDetailsContentView() { + PreviewProvider { + ProjectDetailsView(projectDetailsViewModel = it.createProjectDetails(previewState = PreviewState.Data.Content)) + } +} + +@Preview +@Composable +fun PreviewProjectsDetailsEmptyView() { + PreviewProvider { + ProjectDetailsView(projectDetailsViewModel = it.createProjectDetails(previewState = PreviewState.Data.Empty)) + } +} + +@Preview +@Composable +fun PreviewProjectsDetailsLoadingView() { + PreviewProvider { + ProjectDetailsView(projectDetailsViewModel = it.createProjectDetails(previewState = PreviewState.Loading)) + } +} + +@Preview +@Composable +fun PreviewProjectsDetailsErrorView() { + PreviewProvider { + ProjectDetailsView(projectDetailsViewModel = it.createProjectDetails(previewState = PreviewState.Error)) + } +} \ No newline at end of file diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsContentView.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsContentView.kt index 2950421..e0b0853 100644 --- a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsContentView.kt +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsContentView.kt @@ -1,6 +1,8 @@ package com.mirego.kmp.boilerplate.app.ui.projects import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,8 +17,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -103,6 +107,13 @@ private fun ProjectsListView(viewModel: VMDListViewModel) { @Composable private fun ItemView(item: ProjectItem) { Column( + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .clickable( + onClick = { item.tapAction.invoke() }, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple() + ), verticalArrangement = Arrangement.spacedBy(padding) ) { VMDImage( diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsView.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsView.kt index 4b0902c..a63de14 100644 --- a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsView.kt +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/projects/ProjectsView.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import com.mirego.kmp.boilerplate.app.ui.common.ErrorView +import com.mirego.kmp.boilerplate.app.ui.navigation.NavigationView import com.mirego.kmp.boilerplate.app.ui.preview.PreviewProvider import com.mirego.kmp.boilerplate.app.ui.theme.PrimaryBlack import com.mirego.kmp.boilerplate.usecase.preview.PreviewState @@ -21,13 +22,15 @@ import com.mirego.trikot.viewmodels.declarative.compose.extensions.observeAsStat @Composable fun ProjectsView(projectsViewModel: ProjectsViewModel) { val viewModel: ProjectsViewModel by projectsViewModel.observeAsState() - Box( - modifier = Modifier - .fillMaxSize() - .navigationBarsPadding() - .background(Color.PrimaryBlack), - ) { - ContentView(viewModel = viewModel) + NavigationView(navigationViewModel = viewModel) { + Box( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .background(Color.PrimaryBlack), + ) { + ContentView(viewModel = viewModel) + } } } diff --git a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/theme/Colors.kt b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/theme/Colors.kt index 3543523..5b664be 100644 --- a/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/theme/Colors.kt +++ b/androidApp/src/main/java/com/mirego/kmp/boilerplate/app/ui/theme/Colors.kt @@ -5,4 +5,4 @@ import androidx.compose.ui.graphics.Color val Color.Companion.PrimaryBlack: Color get() = Color(0xFF211E25) val Color.Companion.AccentOrange: Color get() = Color(0xFFFF3829) val Color.Companion.ShimmerBackground: Color get() = Color(0xFF4C474f) -val Color.Companion.ShimmerHighlight: Color get() = Color(0xFF938C96) +val Color.Companion.ShimmerHighlight: Color get() = Color(0xFF938C96) \ No newline at end of file diff --git a/androidApp/src/main/res/drawable/baseline_close_24.xml b/androidApp/src/main/res/drawable/baseline_close_24.xml new file mode 100644 index 0000000..70db409 --- /dev/null +++ b/androidApp/src/main/res/drawable/baseline_close_24.xml @@ -0,0 +1,5 @@ + + +