diff --git a/CHANGELOG.md b/CHANGELOG.md index a4560032..56265324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Swipe to remove ### Changed +- New adaptative layout +- Task detail screen is used by default now +- New today indicator +- Cleaner and more condensed design +- Header is not expandable now, it's been replaced by the select day dialog ### Deprecated diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 12516831..045424f9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,7 +2,6 @@ import com.costular.atomtasks.Versioning plugins { id("atomtasks.android.application") - kotlin("kapt") id("kotlin-parcelize") id("com.google.gms.google-services") id("com.google.firebase.crashlytics") @@ -67,10 +66,6 @@ android { } } -kapt { - correctErrorTypes = true -} - configurations { androidTestImplementation { exclude(group = "io.mockk", module = "mockk-agent-jvm") @@ -84,20 +79,17 @@ dependencies { implementation(projects.core.logging) implementation(project(":data")) implementation(project(":feature:agenda")) - implementation(project(":feature:createtask")) implementation(project(":feature:settings")) implementation(projects.common.tasks) - implementation(projects.feature.edittask) implementation(projects.feature.postponeTask) implementation(projects.feature.detail) - implementation(libs.fragment) implementation(libs.compose.activity) implementation(libs.compose.ui) implementation(libs.compose.material3) implementation(libs.compose.material3.windowsize) + implementation(libs.compose.adaptative.navigation) implementation(libs.compose.material.icons) - implementation(libs.accompanist.systemui) implementation(libs.compose.ui.tooling) implementation(libs.androidx.core) implementation(libs.appcompat) @@ -105,7 +97,7 @@ dependencies { implementation(libs.viewmodel) implementation(libs.hilt) implementation(libs.profileinstaller) - kapt(libs.hilt.compiler) + ksp(libs.hilt.compiler) implementation(libs.hilt.work) implementation(libs.hilt.navigation.compose) implementation(libs.startup) @@ -115,7 +107,9 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) implementation(libs.firebase.crashlytics) - implementation(libs.compose.destinations) + implementation(libs.compose.destinations.core) + implementation(libs.compose.destinations.bottomsheet) + ksp(libs.compose.destinations.ksp) testImplementation(libs.android.junit) testImplementation(libs.junit) diff --git a/app/src/main/java/com/costular/atomtasks/ui/AppNavigation.kt b/app/src/main/java/com/costular/atomtasks/ui/AppNavigation.kt index 60b3e0eb..27230cee 100644 --- a/app/src/main/java/com/costular/atomtasks/ui/AppNavigation.kt +++ b/app/src/main/java/com/costular/atomtasks/ui/AppNavigation.kt @@ -2,53 +2,14 @@ package com.costular.atomtasks.ui import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.navigation.NavController -import androidx.navigation.NavDestination -import androidx.navigation.NavDestination.Companion.hierarchy import com.costular.atomtasks.ui.home.AppNavigator import com.costular.atomtasks.ui.home.AtomAppState import com.ramcosta.composedestinations.DestinationsNavHost -import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine +import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.navigation.dependency +import com.ramcosta.composedestinations.rememberNavHostEngine import com.ramcosta.composedestinations.scope.DestinationScopeWithNoDependencies -import com.ramcosta.composedestinations.spec.NavGraphSpec - -@Stable -@Composable -fun NavController.currentScreenAsState(): State { - val selectedItem = remember { mutableStateOf(NavGraphs.agenda) } - - DisposableEffect(this) { - val listener = NavController.OnDestinationChangedListener { _, destination, _ -> - selectedItem.value = destination.navGraph() - } - addOnDestinationChangedListener(listener) - - onDispose { - removeOnDestinationChangedListener(listener) - } - } - - return selectedItem -} - -fun NavDestination.navGraph(): NavGraphSpec { - hierarchy.forEach { destination -> - NavGraphs.root.nestedNavGraphs.forEach { navGraph -> - if (destination.route == navGraph.route) { - return navGraph - } - } - } - - throw NavigationError(route) -} fun DestinationScopeWithNoDependencies<*>.currentNavigator(): AppNavigator { return AppNavigator(navController) @@ -62,13 +23,12 @@ internal fun AppNavigation( modifier: Modifier = Modifier, ) { DestinationsNavHost( - engine = rememberAnimatedNavHostEngine(), + engine = rememberNavHostEngine(), navController = appState.navController, - navGraph = NavGraphs.root, + navGraph = NavGraphs.main, modifier = modifier, dependenciesContainerBuilder = { dependency(currentNavigator()) - dependency(appState.windowSizeClass) dependency(fabClick) }, ) diff --git a/app/src/main/java/com/costular/atomtasks/ui/MainGraph.kt b/app/src/main/java/com/costular/atomtasks/ui/MainGraph.kt new file mode 100644 index 00000000..a10b48eb --- /dev/null +++ b/app/src/main/java/com/costular/atomtasks/ui/MainGraph.kt @@ -0,0 +1,15 @@ +package com.costular.atomtasks.ui + +import com.ramcosta.composedestinations.annotation.ExternalNavGraph +import com.ramcosta.composedestinations.annotation.NavHostGraph +import com.ramcosta.composedestinations.generated.agenda.navgraphs.AgendaNavGraph +import com.ramcosta.composedestinations.generated.detail.navgraphs.TaskDetailNavGraph +import com.ramcosta.composedestinations.generated.settings.navgraphs.SettingsNavGraph + +@NavHostGraph +annotation class MainGraph { + @ExternalNavGraph + @ExternalNavGraph() + @ExternalNavGraph(start = true) + companion object Includes +} diff --git a/app/src/main/java/com/costular/atomtasks/ui/NavGraphs.kt b/app/src/main/java/com/costular/atomtasks/ui/NavGraphs.kt deleted file mode 100644 index 29a31388..00000000 --- a/app/src/main/java/com/costular/atomtasks/ui/NavGraphs.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.costular.atomtasks.ui - -import com.atomtasks.feature.detail.destinations.TaskDetailScreenDestination -import com.costular.atomtasks.agenda.destinations.AgendaScreenDestination -import com.costular.atomtasks.agenda.destinations.TasksActionsBottomSheetDestination -import com.costular.atomtasks.createtask.destinations.CreateTaskScreenDestination -import com.costular.atomtasks.feature.edittask.destinations.EditTaskScreenDestination -import com.costular.atomtasks.settings.destinations.SettingsScreenDestination -import com.costular.atomtasks.settings.destinations.ThemeSelectorScreenDestination -import com.ramcosta.composedestinations.spec.DestinationSpec -import com.ramcosta.composedestinations.spec.NavGraphSpec -import com.ramcosta.composedestinations.spec.Route - -object NavGraphs { - - val agenda = object : NavGraphSpec { - override val route: String = "agenda" - - override val startRoute: Route = AgendaScreenDestination - - override val destinationsByRoute = listOf>( - AgendaScreenDestination, - TasksActionsBottomSheetDestination, - TaskDetailScreenDestination, - ).associateBy { it.route } - } - - val createTask = object : NavGraphSpec { - override val route: String = "createtask" - - override val startRoute: Route = CreateTaskScreenDestination - - override val destinationsByRoute: Map> = - listOf>( - CreateTaskScreenDestination, - ).associateBy { it.route } - } - - val settings = object : NavGraphSpec { - override val route: String = "settings" - - override val startRoute: Route = SettingsScreenDestination - - override val destinationsByRoute: Map> = - listOf>( - SettingsScreenDestination, - ThemeSelectorScreenDestination, - ).associateBy { it.route } - } - - val editTask = object : NavGraphSpec { - override val route: String = "edittasks" - - override val startRoute: Route = EditTaskScreenDestination - - override val destinationsByRoute: Map> = - listOf>( - EditTaskScreenDestination, - ).associateBy { it.route } - } - - val root = object : NavGraphSpec { - override val route: String = "root" - - override val startRoute: Route = agenda - - override val destinationsByRoute: Map> = emptyMap() - - override val nestedNavGraphs: List = listOf( - agenda, - createTask, - settings, - editTask, - ) - } -} diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/AppNavigator.kt b/app/src/main/java/com/costular/atomtasks/ui/home/AppNavigator.kt index 6f200654..17c8f747 100644 --- a/app/src/main/java/com/costular/atomtasks/ui/home/AppNavigator.kt +++ b/app/src/main/java/com/costular/atomtasks/ui/home/AppNavigator.kt @@ -1,13 +1,11 @@ package com.costular.atomtasks.ui.home import androidx.navigation.NavController -import com.atomtasks.feature.detail.destinations.TaskDetailScreenDestination -import com.costular.atomtasks.agenda.destinations.TasksActionsBottomSheetDestination import com.costular.atomtasks.agenda.ui.AgendaNavigator -import com.costular.atomtasks.createtask.destinations.CreateTaskScreenDestination -import com.costular.atomtasks.feature.edittask.destinations.EditTaskScreenDestination import com.costular.atomtasks.settings.SettingsNavigator -import com.costular.atomtasks.settings.destinations.ThemeSelectorScreenDestination +import com.ramcosta.composedestinations.generated.agenda.destinations.TasksActionsBottomSheetDestination +import com.ramcosta.composedestinations.generated.detail.destinations.TaskDetailScreenDestination +import com.ramcosta.composedestinations.generated.settings.destinations.ThemeSelectorScreenDestination import com.ramcosta.composedestinations.utils.toDestinationsNavigator import java.time.LocalDate @@ -19,14 +17,6 @@ class AppNavigator( navController.toDestinationsNavigator() } - override fun navigateToCreateTask(date: String) { - destinationsNavigator.navigate(CreateTaskScreenDestination(date)) - } - - override fun navigateToEditTask(taskId: Long) { - destinationsNavigator.navigate(EditTaskScreenDestination(taskId)) - } - override fun navigateToDetailScreenForCreateTask(date: String) { destinationsNavigator.navigate( TaskDetailScreenDestination( diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/AtomAppState.kt b/app/src/main/java/com/costular/atomtasks/ui/home/AtomAppState.kt index fa352173..3fe1076b 100644 --- a/app/src/main/java/com/costular/atomtasks/ui/home/AtomAppState.kt +++ b/app/src/main/java/com/costular/atomtasks/ui/home/AtomAppState.kt @@ -1,44 +1,53 @@ package com.costular.atomtasks.ui.home -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember -import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController -import androidx.navigation.compose.currentBackStackEntryAsState -import com.ramcosta.composedestinations.spec.NavGraphSpec +import com.ramcosta.composedestinations.generated.agenda.destinations.AgendaScreenDestination +import com.ramcosta.composedestinations.generated.settings.destinations.SettingsScreenDestination +import com.ramcosta.composedestinations.generated.settings.destinations.ThemeSelectorScreenDestination +import com.ramcosta.composedestinations.spec.DestinationSpec +import com.ramcosta.composedestinations.utils.currentDestinationAsState @Composable fun rememberAtomAppState( navController: NavHostController, - windowSizeClass: WindowSizeClass, ): AtomAppState { - return remember(navController, windowSizeClass) { - AtomAppState(navController, windowSizeClass) + return remember(navController) { + AtomAppState(navController) } } @Stable class AtomAppState( val navController: NavHostController, - val windowSizeClass: WindowSizeClass, ) { - val currentDestination: NavBackStackEntry? - @Composable get() = navController.currentBackStackEntryAsState().value + val currentDestination: DestinationSpec? + @Composable get() = navController.currentDestinationAsState().value - val atomNavigationType: AtomNavigationType - get() = when (windowSizeClass.widthSizeClass) { - WindowWidthSizeClass.Compact -> - AtomNavigationType.BOTTOM_NAVIGATION - WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded -> - AtomNavigationType.RAIL_NAVIGATION - else -> AtomNavigationType.BOTTOM_NAVIGATION + val shouldShowNavigation: Boolean + @Composable get() = currentDestination?.route in listOf( + AgendaScreenDestination, + SettingsScreenDestination, + ThemeSelectorScreenDestination, + ).map { it.route } + + val navigationLayoutType: NavigationSuiteType + @Composable get() { + val adaptiveInfo = currentWindowAdaptiveInfo() + return if (!shouldShowNavigation) { + NavigationSuiteType.None + } else { + NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) + } } - fun navigateToTopLevelDestination(selected: NavGraphSpec) { + fun navigateToTopLevelDestination(selected: DestinationSpec) { navController.navigate(selected.route) { launchSingleTop = true restoreState = true diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/AtomBottomNavigation.kt b/app/src/main/java/com/costular/atomtasks/ui/home/AtomBottomNavigation.kt deleted file mode 100644 index c82e34c2..00000000 --- a/app/src/main/java/com/costular/atomtasks/ui/home/AtomBottomNavigation.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.costular.atomtasks.ui.home - -import androidx.compose.animation.Crossfade -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import com.ramcosta.composedestinations.spec.NavGraphSpec - -@Composable -fun AtomBottomNavigation( - selectedNavigation: NavGraphSpec, - onNavigationSelected: (NavGraphSpec) -> Unit, - modifier: Modifier = Modifier, -) { - NavigationBar( - modifier = modifier, - ) { - HomeNavigationDestination.values().forEach { destination -> - NavigationBarItem( - selected = selectedNavigation == destination.screen, - onClick = { - onNavigationSelected(destination.screen) - }, - icon = { - HomeNavigationItemIcon( - destination = destination, - selected = selectedNavigation == destination.screen, - ) - }, - label = { Text(stringResource(destination.labelResId)) }, - ) - } - } -} - -@Composable -internal fun HomeNavigationItemIcon( - destination: HomeNavigationDestination, - selected: Boolean, -) { - Crossfade(targetState = selected) { - Icon( - imageVector = if (selected) destination.selectedIcon else destination.icon, - contentDescription = stringResource(destination.contentDescriptionResId), - ) - } -} diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/AtomNavigationRail.kt b/app/src/main/java/com/costular/atomtasks/ui/home/AtomNavigationRail.kt deleted file mode 100644 index d5785623..00000000 --- a/app/src/main/java/com/costular/atomtasks/ui/home/AtomNavigationRail.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.costular.atomtasks.ui.home - -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.material3.NavigationRail -import androidx.compose.material3.NavigationRailItem -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import com.ramcosta.composedestinations.spec.NavGraphSpec - -@Composable -fun AtomNavigationRail( - selectedNavigation: NavGraphSpec, - onNavigationSelected: (NavGraphSpec) -> Unit, - modifier: Modifier = Modifier, - header: @Composable ColumnScope.() -> Unit = {}, -) { - NavigationRail( - modifier = modifier, - header = header, - ) { - Spacer(Modifier.weight(1f)) - - HomeNavigationDestination.values().forEach { destination -> - val isSelected = selectedNavigation == destination.screen - - NavigationRailItem( - selected = isSelected, - onClick = { onNavigationSelected(destination.screen) }, - icon = { - HomeNavigationItemIcon( - destination = destination, - selected = isSelected, - ) - }, - label = { - Text(stringResource(destination.labelResId)) - }, - ) - } - - Spacer(Modifier.weight(1f)) - } -} diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/AtomNavigationType.kt b/app/src/main/java/com/costular/atomtasks/ui/home/AtomNavigationType.kt deleted file mode 100644 index 530a6e4b..00000000 --- a/app/src/main/java/com/costular/atomtasks/ui/home/AtomNavigationType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.costular.atomtasks.ui.home - -enum class AtomNavigationType { - BOTTOM_NAVIGATION, RAIL_NAVIGATION; -} diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/Home.kt b/app/src/main/java/com/costular/atomtasks/ui/home/Home.kt index 30f5c1ca..3b794bd1 100644 --- a/app/src/main/java/com/costular/atomtasks/ui/home/Home.kt +++ b/app/src/main/java/com/costular/atomtasks/ui/home/Home.kt @@ -6,82 +6,38 @@ import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.costular.atomtasks.core.ui.DestinationsBottomSheet import com.costular.atomtasks.core.ui.DestinationsScaffold -import com.costular.atomtasks.data.settings.Theme import com.costular.atomtasks.ui.AppNavigation -import com.costular.atomtasks.ui.NavGraphs -import com.costular.atomtasks.ui.currentScreenAsState import com.costular.designsystem.theme.AtomTheme -import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine -import com.ramcosta.composedestinations.spec.NavGraphSpec +import com.ramcosta.composedestinations.generated.agenda.destinations.AgendaScreenDestination +import com.ramcosta.composedestinations.rememberNavHostEngine import com.costular.atomtasks.core.ui.R.string as S -@OptIn(ExperimentalAnimationApi::class) @Composable fun App( - windowSizeClass: WindowSizeClass, + isDarkTheme: Boolean, ) { - val viewModel: AppViewModel = hiltViewModel() - val state by viewModel.state.collectAsStateWithLifecycle() - val isSystemDarkMode = isSystemInDarkTheme() - - val systemUiController = rememberSystemUiController() - val isDarkTheme = remember(state.theme) { - when (state.theme) { - is Theme.Light -> false - is Theme.Dark -> true - is Theme.System -> isSystemDarkMode - } - } - - SideEffect { - systemUiController.setSystemBarsColor( - Color.Transparent, - darkIcons = !isDarkTheme, - ) - } - AtomTheme(darkTheme = isDarkTheme) { - val engine = rememberAnimatedNavHostEngine() + val engine = rememberNavHostEngine() val navController = engine.rememberNavController() Home( atomAppState = rememberAtomAppState( navController = navController, - windowSizeClass = windowSizeClass, ), ) } @@ -93,87 +49,60 @@ fun App( private fun Home( atomAppState: AtomAppState, ) { - val currentSelectedItem by atomAppState.navController.currentScreenAsState() val (fabOnClick, setFabOnClick) = remember { mutableStateOf<(() -> Unit)?>(null) } + val currentDestination = atomAppState.currentDestination - when (atomAppState.atomNavigationType) { - AtomNavigationType.BOTTOM_NAVIGATION -> { - DestinationsScaffold( - bottomBar = { - AtomBottomNavigation( - selectedNavigation = currentSelectedItem, - onNavigationSelected = { selected -> - atomAppState.navigateToTopLevelDestination(selected) - }, - modifier = Modifier.fillMaxWidth(), - ) - }, - floatingActionButton = { - AddTaskFloatingActionButton( - currentSelectedItem = currentSelectedItem, - fabOnclick = fabOnClick, - shouldBeExpanded = true, - ) - }, - navController = atomAppState.navController, - ) { paddingValues -> - AppNavigation( - modifier = Modifier.padding(paddingValues), - appState = atomAppState, - fabClick = setFabOnClick, - ) - } - } - - AtomNavigationType.RAIL_NAVIGATION -> { - DestinationsBottomSheet( - navController = atomAppState.navController, - ) { - Row( - Modifier - .fillMaxSize() - .windowInsetsPadding( - WindowInsets.safeDrawing.only( - WindowInsetsSides.Horizontal, - ), - ), - ) { - AtomNavigationRail( - selectedNavigation = currentSelectedItem, - onNavigationSelected = { selected -> - atomAppState.navigateToTopLevelDestination(selected) - }, - header = { - AddTaskFloatingActionButton( - currentSelectedItem = currentSelectedItem, - fabOnclick = fabOnClick, - shouldBeExpanded = false, - ) - }, - modifier = Modifier.safeDrawingPadding(), - ) - - Spacer(Modifier.width(24.dp)) + NavigationSuiteScaffold( + navigationSuiteItems = { + HomeNavigationDestination.entries.forEach { destination -> + val isCurrentDestination = currentDestination == destination.screen - AppNavigation( - appState = atomAppState, - modifier = Modifier.weight(1f), - fabClick = setFabOnClick, - ) - } + item( + selected = isCurrentDestination, + onClick = { + atomAppState.navigateToTopLevelDestination(destination.screen) + }, + icon = { + HomeNavigationItemIcon( + destination = destination, + selected = isCurrentDestination, + ) + }, + label = { Text(stringResource(destination.labelResId)) }, + ) } + }, + layoutType = atomAppState.navigationLayoutType, + ) { + DestinationsScaffold( + navController = atomAppState.navController, + floatingActionButton = { + AddTaskFloatingActionButton( + shouldBeShown = currentDestination == AgendaScreenDestination, + fabOnclick = fabOnClick, + shouldBeExpanded = true, + ) + }, + ) { padding -> + AppNavigation( + modifier = Modifier + .fillMaxSize() + .padding(padding), + appState = atomAppState, + fabClick = setFabOnClick, + ) } } } @Composable private fun AddTaskFloatingActionButton( - currentSelectedItem: NavGraphSpec, + shouldBeShown: Boolean, shouldBeExpanded: Boolean, fabOnclick: (() -> Unit)?, ) { AnimatedVisibility( - visible = currentSelectedItem == NavGraphs.agenda, + visible = shouldBeShown, enter = scaleIn( animationSpec = tween( easing = FastOutSlowInEasing, diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationDestination.kt b/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationDestination.kt index a8accaa5..8c907a67 100644 --- a/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationDestination.kt +++ b/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationDestination.kt @@ -8,25 +8,26 @@ import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.ViewAgenda import androidx.compose.ui.graphics.vector.ImageVector import com.costular.atomtasks.core.ui.R -import com.costular.atomtasks.ui.NavGraphs -import com.ramcosta.composedestinations.spec.NavGraphSpec +import com.ramcosta.composedestinations.generated.agenda.destinations.AgendaScreenDestination +import com.ramcosta.composedestinations.generated.settings.destinations.SettingsScreenDestination +import com.ramcosta.composedestinations.spec.DirectionDestinationSpec enum class HomeNavigationDestination( - val screen: NavGraphSpec, + val screen: DirectionDestinationSpec, @StringRes val contentDescriptionResId: Int, val icon: ImageVector, val selectedIcon: ImageVector, @StringRes val labelResId: Int, ) { Agenda( - screen = NavGraphs.agenda, + screen = AgendaScreenDestination, contentDescriptionResId = R.string.home_menu_agenda, icon = Icons.Outlined.ViewAgenda, selectedIcon = Icons.Filled.ViewAgenda, labelResId = R.string.home_menu_agenda, ), Settings( - screen = NavGraphs.settings, + screen = SettingsScreenDestination, icon = Icons.Outlined.Settings, selectedIcon = Icons.Filled.Settings, labelResId = R.string.home_menu_settings, diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationItemIcon.kt b/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationItemIcon.kt new file mode 100644 index 00000000..4ceb6937 --- /dev/null +++ b/app/src/main/java/com/costular/atomtasks/ui/home/HomeNavigationItemIcon.kt @@ -0,0 +1,19 @@ +package com.costular.atomtasks.ui.home + +import androidx.compose.animation.Crossfade +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource + +@Composable +internal fun HomeNavigationItemIcon( + destination: HomeNavigationDestination, + selected: Boolean, +) { + Crossfade(targetState = selected, label = "Icon") { selected -> + Icon( + imageVector = if (selected) destination.selectedIcon else destination.icon, + contentDescription = stringResource(destination.contentDescriptionResId), + ) + } +} diff --git a/app/src/main/java/com/costular/atomtasks/ui/home/MainActivity.kt b/app/src/main/java/com/costular/atomtasks/ui/home/MainActivity.kt index 98f9fe0a..af9c00d4 100644 --- a/app/src/main/java/com/costular/atomtasks/ui/home/MainActivity.kt +++ b/app/src/main/java/com/costular/atomtasks/ui/home/MainActivity.kt @@ -1,22 +1,72 @@ package com.costular.atomtasks.ui.home +import android.graphics.Color import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent -import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass -import androidx.core.view.WindowCompat +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.costular.atomtasks.data.settings.Theme import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @AndroidEntryPoint class MainActivity : ComponentActivity() { + + private val viewModel: AppViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) + + var uiState: AppState by mutableStateOf(AppState.Empty) + + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.state + .collect { + uiState = it + } + } + } + + enableEdgeToEdge() setContent { - App(windowSizeClass = calculateWindowSizeClass(this)) + + val isDarkTheme = when (uiState.theme) { + is Theme.Light -> false + is Theme.Dark -> true + is Theme.System -> isSystemInDarkTheme() + } + + DisposableEffect(isDarkTheme) { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + lightScrim = Color.TRANSPARENT, + darkScrim = Color.TRANSPARENT, + detectDarkMode = { isDarkTheme }), + navigationBarStyle = SystemBarStyle.auto( + lightScrim = LightScrim, + darkScrim = DarkScrim, + detectDarkMode = { isDarkTheme }) + ) + + onDispose { } + } + + App(isDarkTheme = isDarkTheme) } } } + +private val LightScrim = Color.argb(0xe6, 0xFF, 0xFF, 0xFF) +private val DarkScrim = Color.argb(0x80, 0x1b, 0x1b, 0x1b) diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 25c7f8e5..348c33ac 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -14,7 +14,6 @@ class AndroidFeatureConventionPlugin : Plugin { pluginManager.apply { apply("com.android.library") apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.kapt") } extensions.configure() { defaultConfig { @@ -39,13 +38,10 @@ class AndroidFeatureConventionPlugin : Plugin { add("implementation", libs.findLibrary("compose.material.icons").get()) add("implementation", libs.findLibrary("compose.ui").get()) add("implementation", libs.findLibrary("compose.ui.tooling").get()) - add("implementation", libs.findLibrary("accompanist.systemui").get()) add("implementation", libs.findLibrary("viewmodel").get()) add("implementation", libs.findLibrary("hilt.navigation.compose").get()) - add("kapt", libs.findLibrary("hilt.compiler").get()) - add("implementation", libs.findLibrary("hilt").get()) - add("kapt", libs.findLibrary("hilt.compiler").get()) - add("implementation", libs.findLibrary("compose.destinations").get()) + add("implementation", libs.findLibrary("compose.destinations.core").get()) + add("implementation", libs.findLibrary("compose.destinations.bottomsheet").get()) add("testImplementation", project(":core:testing")) add("androidTestImplementation", project(":core:testing")) diff --git a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt index 32262c41..f3ba6c6d 100644 --- a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -8,18 +8,17 @@ import org.gradle.kotlin.dsl.getByType class AndroidHiltConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("dagger.hilt.android.plugin") - // KAPT must go last to avoid build warnings. - // See: https://stackoverflow.com/questions/70550883/warning-the-following-options-were-not-recognized-by-any-processor-dagger-f - apply("org.jetbrains.kotlin.kapt") - } + pluginManager.apply("com.google.devtools.ksp") dependencies { "implementation"(libs.findLibrary("hilt").get()) - "kapt"(libs.findLibrary("hilt.compiler").get()) - "kaptAndroidTest"(libs.findLibrary("hilt.compiler").get()) - "kaptTest"(libs.findLibrary("hilt.compiler").get()) + "ksp"(libs.findLibrary("hilt.compiler").get()) + "kspAndroidTest"(libs.findLibrary("hilt.compiler").get()) + "kspTest"(libs.findLibrary("hilt.compiler").get()) + } + + pluginManager.withPlugin("com.android.base") { + pluginManager.apply("dagger.hilt.android.plugin") } } } diff --git a/build-logic/convention/src/main/kotlin/com/costular/atomtasks/Versioning.kt b/build-logic/convention/src/main/kotlin/com/costular/atomtasks/Versioning.kt index 24fbf276..b656c637 100644 --- a/build-logic/convention/src/main/kotlin/com/costular/atomtasks/Versioning.kt +++ b/build-logic/convention/src/main/kotlin/com/costular/atomtasks/Versioning.kt @@ -1,6 +1,6 @@ package com.costular.atomtasks object Versioning { - const val VersionCode = 23 - const val VersionName = "2.6.2" + const val VersionCode = 24 + const val VersionName = "2.6.3" } diff --git a/common/tasks/build.gradle.kts b/common/tasks/build.gradle.kts index 26faebb0..5f6b398b 100644 --- a/common/tasks/build.gradle.kts +++ b/common/tasks/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("atomtasks.android.library.compose") id("atomtasks.detekt") id("atomtasks.android.hilt") + alias(libs.plugins.ksp) } android { @@ -34,16 +35,16 @@ dependencies { implementation(libs.compose.material.icons) implementation(libs.compose.ui) implementation(libs.compose.ui.tooling) - implementation(libs.accompanist.systemui) implementation(libs.viewmodel) implementation(libs.hilt.navigation.compose) implementation(libs.hilt.work) implementation(libs.work) - implementation(libs.compose.destinations) + implementation(libs.compose.destinations.core) + implementation(libs.compose.destinations.bottomsheet) implementation(libs.accompanist.permissions) implementation(libs.room.ktx) - kapt(libs.room.compiler) - kapt(libs.hilt.ext.compiler) + ksp(libs.room.compiler) + ksp(libs.hilt.ext.compiler) api(libs.reordeable) testImplementation(projects.common.tasks) diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index b01aa5db..46ff7036 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("atomtasks.detekt") id("atomtasks.android.library.jacoco") id("dagger.hilt.android.plugin") - kotlin("kapt") + alias(libs.plugins.ksp) } android { @@ -15,7 +15,7 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) implementation(libs.hilt) - kapt(libs.hilt.compiler) + ksp(libs.hilt.compiler) testImplementation(libs.truth) testImplementation(libs.turbine) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 55f13f1d..97bfa867 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("atomtasks.android.library") id("atomtasks.detekt") - id("kotlin-kapt") + alias(libs.plugins.ksp) } android { @@ -11,7 +11,7 @@ android { dependencies { api(libs.coroutines) implementation(libs.hilt) - kapt(libs.hilt.compiler) + ksp(libs.hilt.compiler) testImplementation(libs.truth) } diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 47dd5dd4..7cec73b2 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -2,7 +2,6 @@ plugins { id("atomtasks.android.library") id("atomtasks.android.library.compose") id("kotlin-android") - kotlin("kapt") id("atomtasks.detekt") id("atomtasks.android.library.jacoco") id("atomtasks.android.hilt") @@ -33,7 +32,6 @@ dependencies { implementation(libs.compose.material.icons) implementation(libs.compose.ui) implementation(libs.compose.ui.tooling) - implementation(libs.accompanist.systemui) implementation(libs.viewmodel) implementation(libs.compose.ui.text.fonts) api(libs.calendar) diff --git a/core/designsystem/src/main/java/com/costular/designsystem/components/OutlinedTextField.kt b/core/designsystem/src/main/java/com/costular/designsystem/components/OutlinedTextField.kt deleted file mode 100644 index 76bdea8a..00000000 --- a/core/designsystem/src/main/java/com/costular/designsystem/components/OutlinedTextField.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.costular.designsystem.components - -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.VisualTransformation - -@Composable -fun AtomOutlinedTextField( - value: String, - onValueChange: (String) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - readOnly: Boolean = false, - textStyle: TextStyle = LocalTextStyle.current, - label: @Composable (() -> Unit)? = null, - placeholder: @Composable (() -> Unit)? = null, - leadingIcon: @Composable (() -> Unit)? = null, - trailingIcon: @Composable (() -> Unit)? = null, - isError: Boolean = false, - visualTransformation: VisualTransformation = VisualTransformation.None, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - keyboardActions: KeyboardActions = KeyboardActions.Default, - singleLine: Boolean = false, - maxLines: Int = Int.MAX_VALUE, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - shape: Shape = MaterialTheme.shapes.small, -) { - OutlinedTextField( - value = value, - onValueChange = onValueChange, - modifier = modifier, - enabled = enabled, - readOnly = readOnly, - textStyle = textStyle, - label = label, - placeholder = placeholder, - leadingIcon = leadingIcon, - trailingIcon = trailingIcon, - isError = isError, - visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - singleLine = singleLine, - maxLines = maxLines, - interactionSource = interactionSource, - shape = shape, - colors = OutlinedTextFieldDefaults.colors(), - ) -} diff --git a/core/designsystem/src/main/java/com/costular/designsystem/components/WeekCalendar.kt b/core/designsystem/src/main/java/com/costular/designsystem/components/WeekCalendar.kt index 2f524bab..78bb88db 100644 --- a/core/designsystem/src/main/java/com/costular/designsystem/components/WeekCalendar.kt +++ b/core/designsystem/src/main/java/com/costular/designsystem/components/WeekCalendar.kt @@ -1,13 +1,12 @@ package com.costular.designsystem.components -import androidx.compose.foundation.Canvas +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme @@ -19,7 +18,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.costular.atomtasks.core.ui.date.Day @@ -84,8 +82,18 @@ private fun CalendarDay( CardDefaults.outlinedCardColors() } + val border = if (isToday) { + BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.primary + ) + } else { + CardDefaults.outlinedCardBorder() + } + OutlinedCard( colors = cardColors, + border = border, modifier = modifier .height(70.dp) .clip(RoundedCornerShape(12.dp)) @@ -104,31 +112,10 @@ private fun CalendarDay( text = day.toString(), style = MaterialTheme.typography.titleMedium ) - - if (isToday) { - IndicatorToday( - color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.60f) - ) - } } } } -@Composable -fun IndicatorToday( - modifier: Modifier = Modifier, - color: Color, -) { - Canvas( - modifier = modifier.size(4.dp), - onDraw = { - drawCircle( - color = color, - ) - } - ) -} - @Preview @Composable private fun HorizontalCalendarPreview() { diff --git a/core/jobs/build.gradle.kts b/core/jobs/build.gradle.kts index 78654698..b24ee188 100644 --- a/core/jobs/build.gradle.kts +++ b/core/jobs/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("atomtasks.detekt") id("atomtasks.android.library.jacoco") id("dagger.hilt.android.plugin") - kotlin("kapt") + alias(libs.plugins.ksp) } android { @@ -13,7 +13,7 @@ android { dependencies { implementation(libs.hilt) - kapt(libs.hilt.compiler) + ksp(libs.hilt.compiler) implementation(libs.work) testImplementation(libs.work.testing) diff --git a/core/logging/build.gradle.kts b/core/logging/build.gradle.kts index 50c36b4e..ec580dd0 100644 --- a/core/logging/build.gradle.kts +++ b/core/logging/build.gradle.kts @@ -1,7 +1,6 @@ plugins { id("atomtasks.android.library") id("atomtasks.detekt") - id("kotlin-kapt") } android { diff --git a/core/notifications/build.gradle.kts b/core/notifications/build.gradle.kts index 746ef55e..544665c1 100644 --- a/core/notifications/build.gradle.kts +++ b/core/notifications/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("atomtasks.detekt") id("atomtasks.android.library.jacoco") id("atomtasks.android.hilt") - kotlin("kapt") } android { diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index 697451c4..0b96a5fd 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -1,7 +1,6 @@ plugins { id("atomtasks.android.library") id("kotlin-android") - kotlin("kapt") id("atomtasks.detekt") id("atomtasks.android.library.jacoco") id("atomtasks.android.hilt") @@ -11,10 +10,6 @@ android { namespace = "com.costular.atomtasks.core.testing" } -kapt { - correctErrorTypes = true -} - dependencies { implementation(project(":core")) implementation(project(":data")) diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index e8c8a945..81f0bd2d 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -12,7 +12,7 @@ android { dependencies { api(project(":core")) - api(libs.compose.bom) + api(platform(libs.compose.bom.alpha)) api(libs.lifecycle.compose) implementation(libs.androidx.core) @@ -20,7 +20,8 @@ dependencies { implementation(libs.compose.ui) implementation(libs.compose.material3) implementation(libs.compose.material3.windowsize) - implementation(libs.compose.destinations) + implementation(libs.compose.destinations.core) + implementation(libs.compose.destinations.bottomsheet) implementation(libs.compose.ui.tooling) implementation(libs.viewmodel) diff --git a/core/ui/src/main/java/com/costular/atomtasks/core/ui/DestinationsScaffold.kt b/core/ui/src/main/java/com/costular/atomtasks/core/ui/DestinationsScaffold.kt index d003234a..f95aed97 100644 --- a/core/ui/src/main/java/com/costular/atomtasks/core/ui/DestinationsScaffold.kt +++ b/core/ui/src/main/java/com/costular/atomtasks/core/ui/DestinationsScaffold.kt @@ -16,13 +16,11 @@ import androidx.navigation.plusAssign @Composable fun DestinationsScaffold( navController: NavHostController, - bottomBar: @Composable () -> Unit, floatingActionButton: @Composable () -> Unit, content: @Composable (PaddingValues) -> Unit, ) { DestinationsBottomSheet(navController = navController) { Scaffold( - bottomBar = bottomBar, floatingActionButton = floatingActionButton, ) { content(it) diff --git a/core/ui/src/main/res/values-es/strings.xml b/core/ui/src/main/res/values-es/strings.xml index 68a06dd5..3f235765 100644 --- a/core/ui/src/main/res/values-es/strings.xml +++ b/core/ui/src/main/res/values-es/strings.xml @@ -20,6 +20,7 @@ Editar tarea Hoy Repetir + No se repite Diario Días laborables Semanal @@ -97,4 +98,5 @@ Actualizar tarea periódica Esta tarea Esta tarea y las posteriores + Título de la tarea diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 23551e91..9a04db77 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -30,7 +30,7 @@ New task Want your tasks in order? Long press and drag! Today - Just once + Does not repeat Daily Weekdays Weekly @@ -54,6 +54,7 @@ Every Year + Add title Date Reminder Reminder diff --git a/core/ui/tasks/build.gradle.kts b/core/ui/tasks/build.gradle.kts index da9e7a6f..15b2e31f 100644 --- a/core/ui/tasks/build.gradle.kts +++ b/core/ui/tasks/build.gradle.kts @@ -25,7 +25,8 @@ dependencies { implementation(libs.compose.ui) implementation(libs.compose.material3) implementation(libs.compose.material3.windowsize) - implementation(libs.compose.destinations) + implementation(libs.compose.destinations.core) + implementation(libs.compose.destinations.bottomsheet) implementation(libs.compose.ui.tooling) implementation(libs.compose.material.icons) diff --git a/core/ui/tasks/src/androidTest/java/com/costular/atomtasks/core/ui/tasks/TaskCardTest.kt b/core/ui/tasks/src/androidTest/java/com/costular/atomtasks/core/ui/tasks/TaskCardTest.kt index a6b7001f..76886d65 100644 --- a/core/ui/tasks/src/androidTest/java/com/costular/atomtasks/core/ui/tasks/TaskCardTest.kt +++ b/core/ui/tasks/src/androidTest/java/com/costular/atomtasks/core/ui/tasks/TaskCardTest.kt @@ -23,9 +23,10 @@ class TaskCardTest : AndroidTest() { isFinished = false, reminder = null, onMark = onMarkCallback, - onOpen = {}, + onClick = {}, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } @@ -48,9 +49,10 @@ class TaskCardTest : AndroidTest() { isFinished = true, reminder = null, onMark = {}, - onOpen = onClickCallback, + onClick = onClickCallback, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } diff --git a/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskCard.kt b/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskCard.kt index 19d1d927..c13df8c5 100644 --- a/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskCard.kt +++ b/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskCard.kt @@ -22,10 +22,12 @@ import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Alarm +import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.Repeat import androidx.compose.material3.CardDefaults import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -74,7 +76,8 @@ fun TaskCard( recurrenceType: RecurrenceType?, reminder: Reminder?, onMark: () -> Unit, - onOpen: () -> Unit, + onClick: () -> Unit, + onClickMore: () -> Unit, isBeingDragged: Boolean, modifier: Modifier = Modifier, ) { @@ -91,7 +94,7 @@ fun TaskCard( ) { !isFinished && (reminder != null || recurrenceType != null) } ElevatedCard( - onClick = onOpen, + onClick = onClick, modifier = modifier, interactionSource = mutableInteractionSource, colors = CardDefaults.cardColors(), @@ -100,9 +103,11 @@ fun TaskCard( val recurringInlineContent = recurringInline(contentColor) Row( - modifier = Modifier.padding(AppTheme.dimens.spacingLarge), + modifier = Modifier.padding(vertical = AppTheme.dimens.spacingMedium), verticalAlignment = Alignment.CenterVertically, ) { + Spacer(Modifier.width(AppTheme.dimens.spacingLarge)) + Markable( isMarked = isFinished, borderColor = contentColor, @@ -113,7 +118,9 @@ fun TaskCard( Spacer(modifier = Modifier.width(AppTheme.dimens.spacingLarge)) - Column { + Column( + modifier = Modifier.weight(1f), + ) { TaskTitle(isFinished = isFinished, title = title) if (shouldShowExtraDetails) { @@ -130,6 +137,15 @@ fun TaskCard( ) } } + + IconButton( + onClick = onClickMore, + modifier = Modifier + .align(Alignment.Top) + .padding(end = AppTheme.dimens.spacingSmall) + ) { + Icon(imageVector = Icons.Outlined.MoreVert, contentDescription = null) + } } } } @@ -144,9 +160,9 @@ private fun ColumnScope.TaskDetails( ) { val recurringContent = if (recurrenceType != null) { val label = recurrenceType.localized() - com.costular.atomtasks.core.ui.tasks.RecurringContent.Recurring(label) + RecurringContent.Recurring(label) } else { - com.costular.atomtasks.core.ui.tasks.RecurringContent.None + RecurringContent.None } val hasReminder = reminder != null @@ -156,7 +172,10 @@ private fun ColumnScope.TaskDetails( if (reminder != null) { Row { val alarmText = buildAnnotatedString { - appendInlineContent(com.costular.atomtasks.core.ui.tasks.ReminderIconId, "[alarm]") + appendInlineContent( + ReminderIconId, + "[alarm]" + ) append(" ") append(reminder.time.ofLocalizedTime()) } @@ -184,7 +203,10 @@ private fun ColumnScope.TaskDetails( val content = recurringContent as RecurringContent.Recurring val recurringText = buildAnnotatedString { - appendInlineContent(com.costular.atomtasks.core.ui.tasks.RecurringIconId, "[recurring]") + appendInlineContent( + RecurringIconId, + "[recurring]" + ) append(" ") append(content.recurrenceLabel) } @@ -315,10 +337,11 @@ private fun TaskCardPreview( isFinished = task.isDone, recurrenceType = task.recurrenceType, onMark = {}, - onOpen = {}, + onClick = {}, reminder = task.reminder, isBeingDragged = false, modifier = Modifier.fillMaxWidth(), + onClickMore = {}, ) } } diff --git a/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskList.kt b/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskList.kt index 7e4b643a..68c49027 100644 --- a/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskList.kt +++ b/core/ui/tasks/src/main/java/com/costular/atomtasks/core/ui/tasks/TaskList.kt @@ -1,30 +1,53 @@ package com.costular.atomtasks.core.ui.tasks -import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +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.PaddingValues 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.LazyItemScope import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxState +import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.Text +import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +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.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.view.HapticFeedbackConstantsCompat import com.costular.atomtasks.core.ui.R import com.costular.atomtasks.core.ui.utils.VariantsPreview import com.costular.atomtasks.tasks.model.Reminder import com.costular.atomtasks.tasks.model.Task import com.costular.designsystem.theme.AppTheme import com.costular.designsystem.theme.AtomTheme +import kotlinx.coroutines.delay import java.time.LocalDate import java.time.LocalTime import org.burnoutcrew.reorderable.ReorderableItem @@ -33,11 +56,14 @@ import org.burnoutcrew.reorderable.detectReorderAfterLongPress import org.burnoutcrew.reorderable.rememberReorderableLazyListState import org.burnoutcrew.reorderable.reorderable -@OptIn(ExperimentalFoundationApi::class) +private const val MillisecondsToResetSwipeBoxState = 1000L + @Composable fun TaskList( tasks: List, onClick: (Task) -> Unit, + onClickMore: (Task) -> Unit, + onDeleteTask: (Task) -> Unit, onMarkTask: (taskId: Long, isDone: Boolean) -> Unit, state: ReorderableLazyListState, modifier: Modifier = Modifier, @@ -55,25 +81,115 @@ fun TaskList( verticalArrangement = Arrangement.spacedBy(8.dp), ) { items(tasks, { it.id }) { task -> - ReorderableItem(state, key = task.id) { isDragging -> - TaskCard( - title = task.name, - onMark = { onMarkTask(task.id, !task.isDone) }, - onOpen = { onClick(task) }, - reminder = task.reminder, - isFinished = task.isDone, - recurrenceType = task.recurrenceType, - modifier = Modifier - .fillMaxWidth() - .animateItemPlacement(), - isBeingDragged = isDragging, - ) - } + TaskItem( + state = state, + task = task, + onDeleteTask = onDeleteTask, + onMarkTask = onMarkTask, + onClick = onClick, + onClickMore = onClickMore + ) } } } } +@Composable +private fun LazyItemScope.TaskItem( + state: ReorderableLazyListState, + task: Task, + onDeleteTask: (Task) -> Unit, + onMarkTask: (taskId: Long, isDone: Boolean) -> Unit, + onClick: (Task) -> Unit, + onClickMore: (Task) -> Unit +) { + val dismissState = rememberSwipeToDismissBoxState() + + SwipeToDismissBox( + state = dismissState, + enableDismissFromEndToStart = true, + enableDismissFromStartToEnd = false, + backgroundContent = { + TaskRemoveBackground(dismissState) + }, + ) { + when (dismissState.currentValue) { + SwipeToDismissBoxValue.EndToStart -> onDeleteTask(task) + SwipeToDismissBoxValue.StartToEnd -> Unit + SwipeToDismissBoxValue.Settled -> Unit + } + + ReorderableItem(state, key = task.id) { isDragging -> + TaskCard( + title = task.name, + onMark = { onMarkTask(task.id, !task.isDone) }, + onClick = { onClick(task) }, + reminder = task.reminder, + isFinished = task.isDone, + recurrenceType = task.recurrenceType, + modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), + isBeingDragged = isDragging, + onClickMore = { onClickMore(task) }, + ) + } + } +} + +@Composable +private fun TaskRemoveBackground( + state: SwipeToDismissBoxState, +) { + val view = LocalView.current + val scale by + animateFloatAsState( + targetValue = if (state.targetValue == SwipeToDismissBoxValue.Settled) 0.75f else 1f, + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing, + ) + ) + + val swipeTargetValue by remember { + derivedStateOf { state.targetValue } + } + + if (state.currentValue != SwipeToDismissBoxValue.Settled) { + LaunchedEffect(Unit) { + delay(MillisecondsToResetSwipeBoxState) + state.reset() + } + } + + LaunchedEffect(swipeTargetValue) { + if (state.targetValue == SwipeToDismissBoxValue.EndToStart) { + view.performHapticFeedback(HapticFeedbackConstantsCompat.GESTURE_START) + } + } + + val (backgroundColor, contentColor) = when (state.dismissDirection) { + SwipeToDismissBoxValue.Settled -> Color.Transparent to Color.Transparent + SwipeToDismissBoxValue.StartToEnd -> Color.Transparent to Color.Transparent + SwipeToDismissBoxValue.EndToStart -> { + MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(backgroundColor, CardDefaults.elevatedShape) + .padding(AppTheme.dimens.contentMargin), + contentAlignment = Alignment.CenterEnd, + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = null, + tint = contentColor, + modifier = Modifier.scale(scale), + ) + } +} + @Composable fun Empty( modifier: Modifier = Modifier, @@ -166,6 +282,8 @@ private fun TaskListPreview() { ), onClick = {}, onMarkTask = { _, _ -> }, + onClickMore = {}, + onDeleteTask = {}, ) } } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index d6f80ba7..bbc4f225 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("atomtasks.android.library") - kotlin("kapt") id("atomtasks.detekt") id("atomtasks.android.library.jacoco") id("dagger.hilt.android.plugin") @@ -20,7 +19,7 @@ dependencies { implementation(projects.core.preferences) implementation(libs.hilt) - kapt(libs.hilt.compiler) + ksp(libs.hilt.compiler) implementation(libs.work) implementation(libs.preferences.datastore) implementation(libs.preferences) diff --git a/data/src/test/java/com/costular/atomtasks/data/settings/GetThemeUseCaseTest.kt b/data/src/test/java/com/costular/atomtasks/data/settings/GetThemeUseCaseTest.kt index dda7782b..993a2755 100644 --- a/data/src/test/java/com/costular/atomtasks/data/settings/GetThemeUseCaseTest.kt +++ b/data/src/test/java/com/costular/atomtasks/data/settings/GetThemeUseCaseTest.kt @@ -6,16 +6,13 @@ import io.mockk.every import io.mockk.mockk import kotlin.time.ExperimentalTime import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @ExperimentalTime class GetThemeUseCaseTest { - private val testScope = TestCoroutineScope() - lateinit var sut: GetThemeUseCase private val repository: SettingsRepository = mockk(relaxed = true) @@ -26,41 +23,38 @@ class GetThemeUseCaseTest { } @Test - fun `should return light when invoke use case given light theme is set`() = - testScope.runBlockingTest { - val theme = Theme.Light - every { repository.observeTheme() } returns flowOf(theme) - - sut.invoke(Unit) - sut.flow.test { - Truth.assertThat(awaitItem()).isEqualTo(theme) - cancelAndIgnoreRemainingEvents() - } + fun `should return light when invoke use case given light theme is set`() = runTest { + val theme = Theme.Light + every { repository.observeTheme() } returns flowOf(theme) + + sut.invoke(Unit) + sut.flow.test { + Truth.assertThat(awaitItem()).isEqualTo(theme) + cancelAndIgnoreRemainingEvents() } + } @Test - fun `should return dark when invoke use case given light theme is set`() = - testScope.runBlockingTest { - val theme = Theme.Dark - every { repository.observeTheme() } returns flowOf(theme) - - sut.invoke(Unit) - sut.flow.test { - Truth.assertThat(awaitItem()).isEqualTo(theme) - cancelAndIgnoreRemainingEvents() - } + fun `should return dark when invoke use case given light theme is set`() = runTest { + val theme = Theme.Dark + every { repository.observeTheme() } returns flowOf(theme) + + sut.invoke(Unit) + sut.flow.test { + Truth.assertThat(awaitItem()).isEqualTo(theme) + cancelAndIgnoreRemainingEvents() } + } @Test - fun `should return system when invoke use case given light theme is set`() = - testScope.runBlockingTest { - val theme = Theme.System - every { repository.observeTheme() } returns flowOf(theme) - - sut.invoke(Unit) - sut.flow.test { - Truth.assertThat(awaitItem()).isEqualTo(theme) - cancelAndIgnoreRemainingEvents() - } + fun `should return system when invoke use case given light theme is set`() = runTest { + val theme = Theme.System + every { repository.observeTheme() } returns flowOf(theme) + + sut.invoke(Unit) + sut.flow.test { + Truth.assertThat(awaitItem()).isEqualTo(theme) + cancelAndIgnoreRemainingEvents() } + } } diff --git a/data/src/test/java/com/costular/atomtasks/data/settings/SetThemeUseCaseTest.kt b/data/src/test/java/com/costular/atomtasks/data/settings/SetThemeUseCaseTest.kt index 030dd619..6958f644 100644 --- a/data/src/test/java/com/costular/atomtasks/data/settings/SetThemeUseCaseTest.kt +++ b/data/src/test/java/com/costular/atomtasks/data/settings/SetThemeUseCaseTest.kt @@ -2,14 +2,11 @@ package com.costular.atomtasks.data.settings import io.mockk.coVerify import io.mockk.mockk -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test class SetThemeUseCaseTest { - private val testScope = TestCoroutineScope() - private val repository: SettingsRepository = mockk(relaxed = true) lateinit var sut: SetThemeUseCase @@ -20,12 +17,11 @@ class SetThemeUseCaseTest { } @Test - fun `should store theme in preferences when change theme`() = - testScope.runBlockingTest { - val theme = Theme.Dark + fun `should store theme in preferences when change theme`() = runTest { + val theme = Theme.Dark - sut.executeSync(SetThemeUseCase.Params(theme)) + sut.executeSync(SetThemeUseCase.Params(theme)) - coVerify { repository.setTheme(theme) } - } + coVerify { repository.setTheme(theme) } + } } diff --git a/data/src/test/java/com/costular/atomtasks/data/settings/SettingsRepositoryImplTest.kt b/data/src/test/java/com/costular/atomtasks/data/settings/SettingsRepositoryImplTest.kt index 34b8b178..46e25e1d 100644 --- a/data/src/test/java/com/costular/atomtasks/data/settings/SettingsRepositoryImplTest.kt +++ b/data/src/test/java/com/costular/atomtasks/data/settings/SettingsRepositoryImplTest.kt @@ -7,7 +7,7 @@ import io.mockk.every import io.mockk.mockk import kotlin.time.ExperimentalTime import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -25,40 +25,37 @@ class SettingsRepositoryImplTest { } @Test - fun `should return light theme when local data source stores light as string`() = - runBlockingTest { - every { settingsLocalDataSource.observeTheme() } returns flowOf(Theme.LIGHT) - - sut.observeTheme().test { - assertThat(awaitItem()).isInstanceOf(Theme.Light::class.java) - cancelAndIgnoreRemainingEvents() - } + fun `should return light theme when local data source stores light as string`() = runTest { + every { settingsLocalDataSource.observeTheme() } returns flowOf(Theme.LIGHT) + + sut.observeTheme().test { + assertThat(awaitItem()).isInstanceOf(Theme.Light::class.java) + cancelAndIgnoreRemainingEvents() } + } @Test - fun `should return dark theme when local data source stores dark as string`() = - runBlockingTest { - every { settingsLocalDataSource.observeTheme() } returns flowOf(Theme.DARK) - - sut.observeTheme().test { - assertThat(awaitItem()).isInstanceOf(Theme.Dark::class.java) - cancelAndIgnoreRemainingEvents() - } + fun `should return dark theme when local data source stores dark as string`() = runTest { + every { settingsLocalDataSource.observeTheme() } returns flowOf(Theme.DARK) + + sut.observeTheme().test { + assertThat(awaitItem()).isInstanceOf(Theme.Dark::class.java) + cancelAndIgnoreRemainingEvents() } + } @Test - fun `should return system theme when local data source stores system as string`() = - runBlockingTest { - every { settingsLocalDataSource.observeTheme() } returns flowOf(Theme.SYSTEM) - - sut.observeTheme().test { - assertThat(awaitItem()).isInstanceOf(Theme.System::class.java) - cancelAndIgnoreRemainingEvents() - } + fun `should return system theme when local data source stores system as string`() = runTest { + every { settingsLocalDataSource.observeTheme() } returns flowOf(Theme.SYSTEM) + + sut.observeTheme().test { + assertThat(awaitItem()).isInstanceOf(Theme.System::class.java) + cancelAndIgnoreRemainingEvents() } + } @Test - fun `should set dark theme successfully`() = runBlockingTest { + fun `should set dark theme successfully`() = runTest { val theme = Theme.Dark sut.setTheme(theme) @@ -67,7 +64,7 @@ class SettingsRepositoryImplTest { } @Test - fun `should set light theme successfully`() = runBlockingTest { + fun `should set light theme successfully`() = runTest { val theme = Theme.Light sut.setTheme(theme) @@ -76,7 +73,7 @@ class SettingsRepositoryImplTest { } @Test - fun `should set system theme successfully`() = runBlockingTest { + fun `should set system theme successfully`() = runTest { val theme = Theme.System sut.setTheme(theme) diff --git a/feature/agenda/build.gradle.kts b/feature/agenda/build.gradle.kts index 9d8acf58..c626f4e7 100644 --- a/feature/agenda/build.gradle.kts +++ b/feature/agenda/build.gradle.kts @@ -2,14 +2,13 @@ plugins { id("atomtasks.android.feature") id("atomtasks.android.library") id("atomtasks.android.library.compose") + alias(libs.plugins.ksp) id("atomtasks.android.library.ksp") id("kotlin-android") - kotlin("kapt") id("kotlin-parcelize") - alias(libs.plugins.ksp) id("atomtasks.detekt") id("atomtasks.android.library.jacoco") - id("dagger.hilt.android.plugin") + id("atomtasks.android.hilt") } android { @@ -18,7 +17,6 @@ android { } ksp { - arg("compose-destinations.mode", "destinations") arg("compose-destinations.moduleName", "agenda") } @@ -44,7 +42,6 @@ configurations { dependencies { implementation(projects.core.analytics) - implementation(libs.compose.destinations) implementation(projects.core.ui.tasks) ksp(libs.compose.destinations.ksp) @@ -72,5 +69,4 @@ dependencies { androidTestImplementation(libs.hilt.android.testing) androidTestImplementation(libs.mockk.android) debugImplementation(libs.compose.ui.manifest) - kaptAndroidTest(libs.hilt.compiler) } diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/actions/TaskActionsBottomSheet.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/actions/TaskActionsBottomSheet.kt index e0123bf8..4e971aaa 100644 --- a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/actions/TaskActionsBottomSheet.kt +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/actions/TaskActionsBottomSheet.kt @@ -23,17 +23,18 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import com.costular.atomtasks.agenda.ui.AgendaGraph import com.costular.atomtasks.core.ui.R import com.costular.designsystem.components.ActionItem import com.costular.designsystem.components.Draggable import com.costular.designsystem.theme.AlphaDivider import com.costular.designsystem.theme.AppTheme import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.bottomsheet.spec.DestinationStyleBottomSheet import com.ramcosta.composedestinations.result.EmptyResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator -import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet -@Destination(style = DestinationStyleBottomSheet::class) +@Destination(style = DestinationStyleBottomSheet::class) @Composable fun ColumnScope.TasksActionsBottomSheet( result: ResultBackNavigator, diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaGraph.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaGraph.kt new file mode 100644 index 00000000..38d43922 --- /dev/null +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaGraph.kt @@ -0,0 +1,7 @@ +package com.costular.atomtasks.agenda.ui + +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph +import com.ramcosta.composedestinations.annotation.NavGraph + +@NavGraph +internal annotation class AgendaGraph diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaHeader.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaHeader.kt index 9ca083cf..e0cabb6b 100644 --- a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaHeader.kt +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaHeader.kt @@ -1,9 +1,5 @@ package com.costular.atomtasks.agenda.ui -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -11,20 +7,16 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material.icons.outlined.Today +import androidx.compose.material.icons.outlined.CalendarMonth import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.rotate import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -33,7 +25,6 @@ import com.costular.atomtasks.core.ui.R import com.costular.atomtasks.core.ui.date.Day import com.costular.atomtasks.core.ui.date.asDay import com.costular.atomtasks.core.ui.utils.DateUtils -import com.costular.designsystem.components.DatePicker import com.costular.designsystem.components.ScreenHeader import com.costular.designsystem.components.WeekCalendar import com.costular.designsystem.theme.AppTheme @@ -53,9 +44,7 @@ internal fun AgendaHeader( selectedDay: Day, onSelectDate: (LocalDate) -> Unit, onSelectToday: () -> Unit, - canExpand: Boolean, - isExpanded: Boolean, - onToggleExpandCollapse: () -> Unit, + onClickCalendar: () -> Unit, ) { val coroutineScope = rememberCoroutineScope() val startDate = remember(selectedDay) { selectedDay.date.minusDays(DaysToShow) } @@ -67,18 +56,8 @@ internal fun AgendaHeader( firstVisibleWeekDate = selectedDay.date, ) - val shadowElevation = if (isExpanded) { - 6.dp - } else { - 2.dp - } - - Surface( - modifier = modifier, - shape = RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp), - shadowElevation = shadowElevation, - ) { - Column { + Surface { + Column(modifier) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, @@ -89,7 +68,12 @@ internal fun AgendaHeader( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .weight(1f) - .clickable(enabled = canExpand, onClick = onToggleExpandCollapse), + .clickable(onClick = { + coroutineScope.launch { + weekCalendarState.animateScrollToWeek(LocalDate.now()) + } + onSelectToday() + }), ) { ScreenHeader( text = selectedDayText, @@ -101,81 +85,33 @@ internal fun AgendaHeader( start = AppTheme.dimens.spacingLarge, ), ) - - val degrees by animateFloatAsState( - targetValue = if (isExpanded) 180f else 0f, - animationSpec = tween( - durationMillis = 200, - easing = FastOutSlowInEasing, - ), - label = "Header collapsable arrow", - ) - - if (canExpand) { - IconButton(onClick = { onToggleExpandCollapse() }) { - Icon( - imageVector = Icons.Default.ExpandMore, - contentDescription = null, - modifier = Modifier.rotate(degrees), - ) - } - } } IconButton( - onClick = { - coroutineScope.launch { - weekCalendarState.animateScrollToWeek(LocalDate.now()) - } - onSelectToday() - }, + onClick = onClickCalendar, modifier = Modifier .padding(end = AppTheme.dimens.spacingLarge) .width(40.dp) .height(40.dp), ) { Icon( - imageVector = Icons.Outlined.Today, - contentDescription = stringResource(R.string.today), + imageVector = Icons.Outlined.CalendarMonth, + contentDescription = stringResource(R.string.home_menu_calendar), ) } } - AnimatedContent( - targetState = isExpanded && canExpand, - label = "Header calendar", - ) { isCollapsed -> - if (isCollapsed) { - ExpandedCalendar(selectedDay, onSelectDate) - } else { - CollapsedCalendar( - selectedDay, - onSelectDate, - weekCalendarState, - ) - } - } + WeekCalendar( + selectedDay, + onSelectDate, + weekCalendarState, + ) } } } @Composable -private fun ExpandedCalendar( - selectedDay: Day, - onSelectDate: (LocalDate) -> Unit, -) { - DatePicker( - modifier = Modifier - .supportWideScreen(480.dp) - .padding(horizontal = AppTheme.dimens.contentMargin) - .padding(bottom = AppTheme.dimens.spacingMedium), - selectedDay = selectedDay.date, - onDateSelected = onSelectDate, - ) -} - -@Composable -private fun CollapsedCalendar( +private fun WeekCalendar( selectedDay: Day, onSelectDate: (LocalDate) -> Unit, weekCalendarState: WeekCalendarState = rememberWeekCalendarState(), @@ -186,7 +122,7 @@ private fun CollapsedCalendar( .padding( start = AppTheme.dimens.spacingLarge, end = AppTheme.dimens.spacingLarge, - bottom = AppTheme.dimens.spacingLarge, + bottom = AppTheme.dimens.spacingSmall, ), selectedDay = selectedDay, onSelectDay = onSelectDate, @@ -202,26 +138,8 @@ private fun HeaderCollapsedPreview() { selectedDay = LocalDate.now().asDay(), onSelectDate = {}, onSelectToday = {}, - canExpand = true, - isExpanded = false, - onToggleExpandCollapse = {}, - modifier = Modifier.fillMaxWidth(), - ) - } -} - -@Preview -@Composable -private fun HeaderExpandedPreview() { - AtomTheme { - AgendaHeader( - selectedDay = LocalDate.now().asDay(), - onSelectDate = {}, - onSelectToday = {}, - canExpand = true, - isExpanded = true, - onToggleExpandCollapse = {}, modifier = Modifier.fillMaxWidth(), + onClickCalendar = {}, ) } } diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaNavigator.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaNavigator.kt index f8cb6c24..3d1f6eca 100644 --- a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaNavigator.kt +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaNavigator.kt @@ -1,14 +1,6 @@ package com.costular.atomtasks.agenda.ui interface AgendaNavigator { - fun navigateToCreateTask( - date: String, - ) - - fun navigateToEditTask( - taskId: Long, - ) - fun navigateToDetailScreenForCreateTask( date: String, ) diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaScreen.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaScreen.kt index a8163570..cdb4825e 100644 --- a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaScreen.kt +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaScreen.kt @@ -5,8 +5,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -19,10 +18,8 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.costular.atomtasks.agenda.actions.TaskActionsResult -import com.costular.atomtasks.agenda.destinations.TasksActionsBottomSheetDestination import com.costular.atomtasks.core.ui.mvi.EventObserver import com.costular.atomtasks.core.ui.utils.DevicesPreview -import com.costular.atomtasks.core.ui.utils.generateWindowSizeClass import com.costular.atomtasks.review.ui.ReviewHandler import com.costular.atomtasks.tasks.dialog.RemoveRecurrentTaskDialog import com.costular.atomtasks.tasks.dialog.RemoveRecurrentTaskResponse.ALL @@ -34,10 +31,12 @@ import com.costular.atomtasks.tasks.model.RecurringRemovalStrategy import com.costular.atomtasks.tasks.model.Task import com.costular.atomtasks.core.ui.tasks.TaskList import com.costular.designsystem.components.CircularLoadingIndicator +import com.costular.designsystem.dialogs.DatePickerDialog import com.costular.designsystem.theme.AppTheme import com.costular.designsystem.theme.AtomTheme import com.costular.designsystem.util.supportWideScreen import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.generated.agenda.destinations.TasksActionsBottomSheetDestination import com.ramcosta.composedestinations.result.NavResult import com.ramcosta.composedestinations.result.ResultRecipient import java.time.LocalDate @@ -48,19 +47,19 @@ import org.burnoutcrew.reorderable.rememberReorderableLazyListState const val TestTagHeader = "AgendaTitle" -@Destination +@Destination( + start = true, +) @Composable fun AgendaScreen( navigator: AgendaNavigator, setFabOnClick: (() -> Unit) -> Unit, resultRecipient: ResultRecipient, - windowSizeClass: WindowSizeClass, ) { AgendaScreen( navigator = navigator, setFabOnClick = setFabOnClick, resultRecipient = resultRecipient, - windowSizeClass = windowSizeClass, viewModel = hiltViewModel(), ) } @@ -70,7 +69,6 @@ internal fun AgendaScreen( navigator: AgendaNavigator, setFabOnClick: (() -> Unit) -> Unit, resultRecipient: ResultRecipient, - windowSizeClass: WindowSizeClass, viewModel: AgendaViewModel = hiltViewModel(), ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -78,19 +76,11 @@ internal fun AgendaScreen( EventObserver(viewModel.uiEvents) { event -> when (event) { is AgendaUiEvents.GoToNewTaskScreen -> { - if (event.shouldShowNewScreen) { - navigator.navigateToDetailScreenForCreateTask(event.date.toString()) - } else { - navigator.navigateToCreateTask(event.date.toString()) - } + navigator.navigateToDetailScreenForCreateTask(event.date.toString()) } is AgendaUiEvents.GoToEditScreen -> { - if (event.shouldShowNewScreen) { - navigator.navigateToDetailScreenToEdit(event.taskId) - } else { - navigator.navigateToEditTask(event.taskId) - } + navigator.navigateToDetailScreenToEdit(event.taskId) } } } @@ -113,13 +103,13 @@ internal fun AgendaScreen( AgendaScreen( state = state, - windowSizeClass = windowSizeClass, onSelectDate = viewModel::setSelectedDay, onSelectToday = viewModel::setSelectedDayToday, onMarkTask = viewModel::onMarkTask, deleteTask = viewModel::deleteTask, deleteRecurringTask = viewModel::deleteRecurringTask, dismissDelete = viewModel::dismissDelete, + onClickOpenCalendarView = viewModel::openCalendarView, openTaskAction = { task -> viewModel.onOpenTaskActions() navigator.openTaskActions( @@ -129,9 +119,15 @@ internal fun AgendaScreen( ) }, - onToggleExpandCollapse = viewModel::toggleHeader, onMoveTask = viewModel::onMoveTask, onDragTask = viewModel::onDragTask, + openTaskDetail = { + viewModel.onEditTask(it.id) + }, + onDeleteTask = { + viewModel.actionDelete(it.id) + }, + onDismissCalendarView = viewModel::dismissCalendarView, ) } @@ -168,20 +164,23 @@ private fun HandleResultRecipients( } } -@Suppress("LongMethod", "LongParameterList") +@OptIn(ExperimentalMaterial3Api::class) +@Suppress("LongMethod", "LongParameterList", "ForbiddenComment") @Composable fun AgendaScreen( state: AgendaState, - windowSizeClass: WindowSizeClass, onSelectDate: (LocalDate) -> Unit, onSelectToday: () -> Unit, + onClickOpenCalendarView: () -> Unit, + onDismissCalendarView: () -> Unit, onMarkTask: (Long, Boolean) -> Unit, deleteTask: (id: Long) -> Unit, deleteRecurringTask: (id: Long, strategy: RecurringRemovalStrategy) -> Unit, dismissDelete: () -> Unit, + openTaskDetail: (Task) -> Unit, openTaskAction: (Task) -> Unit, - onToggleExpandCollapse: () -> Unit, onMoveTask: (Int, Int) -> Unit, + onDeleteTask: (Task) -> Unit, onDragTask: (ItemPosition, ItemPosition) -> Unit, ) { if (state.deleteTaskAction is DeleteTaskAction.Shown) { @@ -209,23 +208,35 @@ fun AgendaScreen( } } + if (state.shouldShowCalendarView) { + DatePickerDialog( + onDismiss = onDismissCalendarView, + currentDate = state.selectedDay.date, + onDateSelected = { + onSelectDate(it) + onDismissCalendarView() + }, + ) + } + Column { AgendaHeader( selectedDay = state.selectedDay, onSelectDate = onSelectDate, - canExpand = windowSizeClass.canExpand, - isExpanded = state.isHeaderExpanded, - onToggleExpandCollapse = onToggleExpandCollapse, modifier = Modifier.fillMaxWidth(), onSelectToday = onSelectToday, + onClickCalendar = onClickOpenCalendarView, ) + TasksContent( state = state, - onOpenTask = openTaskAction, + onOpenTask = openTaskDetail, onMarkTask = onMarkTask, modifier = Modifier.supportWideScreen(), onMoveTask = onMoveTask, onDragTask = onDragTask, + onDeleteTask = onDeleteTask, + onClickTaskMore = openTaskAction, ) } } @@ -234,9 +245,11 @@ fun AgendaScreen( private fun TasksContent( state: AgendaState, onOpenTask: (Task) -> Unit, + onClickTaskMore: (Task) -> Unit, onMarkTask: (Long, Boolean) -> Unit, onDragTask: (ItemPosition, ItemPosition) -> Unit, onMoveTask: (Int, Int) -> Unit, + onDeleteTask: (Task) -> Unit, modifier: Modifier = Modifier, ) { val haptic = LocalHapticFeedback.current @@ -266,6 +279,8 @@ private fun TasksContent( modifier = modifier .fillMaxSize() .testTag("AgendaTaskList"), + onClickMore = onClickTaskMore, + onDeleteTask = onDeleteTask, ) } @@ -283,17 +298,10 @@ private fun TasksContent( } } -private val WindowSizeClass.canExpand: Boolean - get() = - widthSizeClass == WindowWidthSizeClass.Compact || - widthSizeClass == WindowWidthSizeClass.Medium - @Suppress("MagicNumber") @DevicesPreview @Composable fun AgendaPreview() { - val windowSizeClass = generateWindowSizeClass() - AtomTheme { AgendaScreen( state = AgendaState( @@ -336,17 +344,19 @@ fun AgendaPreview() { ), ), ), - windowSizeClass = windowSizeClass, onSelectDate = {}, onSelectToday = {}, onMarkTask = { _, _ -> }, - onToggleExpandCollapse = {}, deleteTask = {}, deleteRecurringTask = { _, _ -> }, dismissDelete = {}, openTaskAction = {}, onMoveTask = { _, _ -> }, onDragTask = { _, _ -> }, + openTaskDetail = {}, + onDismissCalendarView = {}, + onClickOpenCalendarView = {}, + onDeleteTask = {}, ) } } diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaState.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaState.kt index e92c0760..84995137 100644 --- a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaState.kt +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaState.kt @@ -14,6 +14,7 @@ data class AgendaState( val fromToPositions: Pair? = null, val shouldShowCardOrderTutorial: Boolean = false, val shouldShowReviewDialog: Boolean = false, + val shouldShowCalendarView: Boolean = false, ) { companion object { val Empty = AgendaState() diff --git a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaViewModel.kt b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaViewModel.kt index 93506d5e..c3b25aab 100644 --- a/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaViewModel.kt +++ b/feature/agenda/src/main/java/com/costular/atomtasks/agenda/ui/AgendaViewModel.kt @@ -264,4 +264,16 @@ class AgendaViewModel @Inject constructor( taskOrderTutorialDismissedUseCase(Unit) } } + + fun openCalendarView() { + viewModelScope.launch { + setState { copy(shouldShowCalendarView = true) } + } + } + + fun dismissCalendarView() { + viewModelScope.launch { + setState { copy(shouldShowCalendarView = false) } + } + } } diff --git a/feature/createtask/.gitignore b/feature/createtask/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/feature/createtask/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/feature/createtask/build.gradle.kts b/feature/createtask/build.gradle.kts deleted file mode 100644 index d8f60702..00000000 --- a/feature/createtask/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -plugins { - id("atomtasks.android.feature") - id("atomtasks.android.library") - id("atomtasks.android.library.compose") - id("atomtasks.android.library.ksp") - id("kotlin-android") - kotlin("kapt") - alias(libs.plugins.ksp) - id("atomtasks.detekt") - id("atomtasks.android.library.jacoco") -} - -android { - namespace = "com.costular.atomtasks.feature.createtask" - - ksp { - arg("compose-destinations.mode", "destinations") - arg("compose-destinations.moduleName", "createtask") - } - libraryVariants.all { - kotlin.sourceSets { - getByName(name) { - kotlin.srcDir("build/generated/ksp/$name/kotlin") - } - } - } -} - -dependencies { - implementation(projects.common.tasks) - implementation(projects.core.analytics) - implementation(libs.compose.destinations) - ksp(libs.compose.destinations.ksp) - implementation(libs.calendar) - - testImplementation(projects.core.testing) - testImplementation(libs.android.junit) - testImplementation(libs.coroutines.test) - testImplementation(libs.truth) - testImplementation(libs.turbine) - testImplementation(libs.mockk) -} diff --git a/feature/createtask/consumer-rules.pro b/feature/createtask/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/createtask/proguard-rules.pro b/feature/createtask/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/feature/createtask/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/createtask/src/main/AndroidManifest.xml b/feature/createtask/src/main/AndroidManifest.xml deleted file mode 100644 index 8072ee00..00000000 --- a/feature/createtask/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskScreen.kt b/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskScreen.kt deleted file mode 100644 index 56567304..00000000 --- a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskScreen.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.costular.atomtasks.createtask - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.costular.atomtasks.tasks.createtask.CreateTaskExpanded -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet -import java.time.LocalDate - -@OptIn(ExperimentalMaterial3Api::class) -@Destination(style = DestinationStyleBottomSheet::class) -@Composable -fun CreateTaskScreen( - date: String?, - navigator: DestinationsNavigator, -) { - val viewModel: CreateTaskViewModel = hiltViewModel() - val state by viewModel.state.collectAsStateWithLifecycle() - - LaunchedEffect(state) { - if (state is CreateTaskState.Success) { - navigator.navigateUp() - } - } - - val localDate = remember(date) { - if (date != null) { - LocalDate.parse(date) - } else { - LocalDate.now() - } - } - - CreateTaskExpanded( - value = "", - date = localDate, - onSave = { result -> - viewModel.createTask( - result.name, - result.date, - result.reminder, - result.recurrenceType, - ) - }, - modifier = Modifier - .fillMaxWidth() - .navigationBarsPadding() - .imePadding(), - ) -} diff --git a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskState.kt b/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskState.kt deleted file mode 100644 index 7fbe2db2..00000000 --- a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.costular.atomtasks.createtask - -sealed interface CreateTaskState { - - data object Uninitialized : CreateTaskState - data object Saving : CreateTaskState - data object Success : CreateTaskState - data object Failure : CreateTaskState -} diff --git a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskViewModel.kt b/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskViewModel.kt deleted file mode 100644 index de309b72..00000000 --- a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/CreateTaskViewModel.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.costular.atomtasks.createtask - -import androidx.lifecycle.viewModelScope -import com.costular.atomtasks.analytics.AtomAnalytics -import com.costular.atomtasks.core.ui.mvi.MviViewModel -import com.costular.atomtasks.createtask.analytics.CreateTaskAnalytics -import com.costular.atomtasks.tasks.usecase.CreateTaskUseCase -import com.costular.atomtasks.tasks.model.RecurrenceType -import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.LocalDate -import java.time.LocalTime -import javax.inject.Inject -import kotlinx.coroutines.launch - -@HiltViewModel -class CreateTaskViewModel @Inject constructor( - private val createTaskUseCase: CreateTaskUseCase, - private val atomAnalytics: AtomAnalytics, -) : MviViewModel(CreateTaskState.Uninitialized) { - - fun createTask( - name: String, - date: LocalDate, - reminder: LocalTime?, - recurrence: RecurrenceType?, - ) { - viewModelScope.launch { - setState { CreateTaskState.Saving } - - createTaskUseCase( - CreateTaskUseCase.Params( - name = name, - date = date, - reminderEnabled = reminder != null, - reminderTime = reminder, - recurrenceType = recurrence, - ), - ).fold( - ifError = { - setState { CreateTaskState.Failure } - }, - ifResult = { - atomAnalytics.track(CreateTaskAnalytics.TaskCreated) - setState { CreateTaskState.Success } - } - ) - } - } -} diff --git a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/analytics/CreateTaskAnalytics.kt b/feature/createtask/src/main/java/com/costular/atomtasks/createtask/analytics/CreateTaskAnalytics.kt deleted file mode 100644 index 89b0e685..00000000 --- a/feature/createtask/src/main/java/com/costular/atomtasks/createtask/analytics/CreateTaskAnalytics.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.costular.atomtasks.createtask.analytics - -import com.costular.atomtasks.analytics.TrackingEvent - -object CreateTaskAnalytics { - - data object TaskCreated : TrackingEvent(name = "create_task_created") - - data class SetReminder(val time: String) : TrackingEvent( - name = "create_task_reminder_set", - attributes = mapOf("time" to time) - ) - - data object SetDay : TrackingEvent(name = "create_task_day_set") - -} diff --git a/feature/createtask/src/test/java/com/costular/atomtasks/createtask/CreateTaskViewModelTest.kt b/feature/createtask/src/test/java/com/costular/atomtasks/createtask/CreateTaskViewModelTest.kt deleted file mode 100644 index a1671625..00000000 --- a/feature/createtask/src/test/java/com/costular/atomtasks/createtask/CreateTaskViewModelTest.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.costular.atomtasks.createtask - -import app.cash.turbine.test -import com.costular.atomtasks.analytics.AtomAnalytics -import com.costular.atomtasks.core.Either -import com.costular.atomtasks.core.testing.MviViewModelTest -import com.costular.atomtasks.createtask.analytics.CreateTaskAnalytics -import com.costular.atomtasks.tasks.usecase.CreateTaskUseCase -import com.costular.atomtasks.tasks.model.CreateTaskError -import com.costular.atomtasks.tasks.model.RecurrenceType -import com.google.common.truth.Truth.assertThat -import io.mockk.coEvery -import io.mockk.mockk -import io.mockk.verify -import java.time.LocalDate -import java.time.LocalTime -import kotlin.time.ExperimentalTime -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test - -@ExperimentalTime -class CreateTaskViewModelTest : MviViewModelTest() { - - private val createTaskUseCase: CreateTaskUseCase = mockk() - private val atomAnalytics: AtomAnalytics = mockk(relaxed = true) - - lateinit var sut: CreateTaskViewModel - - @Before - fun setUp() { - sut = CreateTaskViewModel( - createTaskUseCase = createTaskUseCase, - atomAnalytics = atomAnalytics, - ) - } - - @Test - fun `should expose success when create task succeed`() = runTest { - val name = "Task's name" - val date = LocalDate.of(2022, 2, 10) - val reminder = LocalTime.of(0, 0) - coEvery { - createTaskUseCase( - CreateTaskUseCase.Params( - name = name, - date = date, - reminderEnabled = true, - reminderTime = reminder, - recurrenceType = RecurrenceType.YEARLY, - ), - ) - } returns Either.Result(Unit) - - sut.createTask( - name = name, - date = date, - reminder = reminder, - recurrence = RecurrenceType.YEARLY, - ) - - sut.state.test { - assertThat(expectMostRecentItem()).isInstanceOf(CreateTaskState.Success::class.java) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `should expose failure when create task fails`() = runTest { - val today = LocalDate.now() - val now = LocalTime.now() - - coEvery { - createTaskUseCase( - CreateTaskUseCase.Params( - "whatever", - date = today, - reminderEnabled = true, - reminderTime = now, - recurrenceType = RecurrenceType.YEARLY, - ) - ) - } returns Either.Error(CreateTaskError.UnknownError) - - sut.createTask( - name = "whatever", - date = today, - reminder = now, - recurrence = RecurrenceType.YEARLY - ) - - sut.state.test { - assertThat(expectMostRecentItem()).isInstanceOf(CreateTaskState.Failure::class.java) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `should track event when create task successfully`() = runTest { - val name = "Task's name" - val date = LocalDate.of(2022, 2, 10) - val reminder = LocalTime.of(0, 0) - coEvery { - createTaskUseCase( - CreateTaskUseCase.Params( - name, - date, - true, - reminder, - RecurrenceType.WEEKDAYS, - ), - ) - } returns Either.Result(Unit) - - sut.createTask( - name = name, - date = date, - reminder = reminder, - recurrence = RecurrenceType.WEEKDAYS, - ) - - verify(exactly = 1) { - atomAnalytics.track(CreateTaskAnalytics.TaskCreated) - } - } -} diff --git a/feature/detail/build.gradle.kts b/feature/detail/build.gradle.kts index d6569ee7..051aee72 100644 --- a/feature/detail/build.gradle.kts +++ b/feature/detail/build.gradle.kts @@ -4,18 +4,16 @@ plugins { id("atomtasks.android.library.compose") id("atomtasks.android.library.ksp") id("kotlin-android") - kotlin("kapt") alias(libs.plugins.ksp) id("atomtasks.detekt") id("atomtasks.android.library.jacoco") - id("dagger.hilt.android.plugin") + id("atomtasks.android.hilt") } android { namespace = "com.costular.atomtasks.feature.detail" ksp { - arg("compose-destinations.mode", "destinations") arg("compose-destinations.moduleName", "detail") } libraryVariants.all { @@ -30,7 +28,6 @@ android { dependencies { implementation(projects.common.tasks) implementation(projects.core.analytics) - implementation(libs.compose.destinations) ksp(libs.compose.destinations.ksp) implementation(libs.accompanist.permissions) diff --git a/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailGraph.kt b/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailGraph.kt new file mode 100644 index 00000000..8e8085c5 --- /dev/null +++ b/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailGraph.kt @@ -0,0 +1,7 @@ +package com.atomtasks.feature.detail + +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph +import com.ramcosta.composedestinations.annotation.NavGraph + +@NavGraph +internal annotation class TaskDetailGraph diff --git a/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailScreen.kt b/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailScreen.kt index 9b17ca70..987f6c3a 100644 --- a/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailScreen.kt +++ b/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailScreen.kt @@ -15,10 +15,9 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope 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.imePadding -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -53,6 +52,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -69,14 +69,12 @@ import com.costular.atomtasks.core.ui.utils.ofLocalizedTime import com.costular.atomtasks.tasks.createtask.RecurrenceTypePickerDialog import com.costular.atomtasks.tasks.format.localized import com.costular.atomtasks.tasks.model.RecurrenceType -import com.costular.designsystem.components.Draggable import com.costular.designsystem.dialogs.DatePickerDialog import com.costular.designsystem.dialogs.TimePickerDialog import com.costular.designsystem.theme.AppTheme import com.costular.designsystem.theme.AtomTheme import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet import java.time.LocalDate import java.time.LocalTime import com.costular.atomtasks.core.ui.R.string as S @@ -85,7 +83,10 @@ private const val FieldMaxLines = 5 private const val FieldMinLines = 1 @OptIn(ExperimentalFoundationApi::class) -@Destination(style = DestinationStyleBottomSheet::class, navArgsDelegate = TaskDetailNavArgs::class) +@Destination( + start = true, + navArgs = TaskDetailNavArgs::class, +) @Composable fun TaskDetailScreen( navigator: DestinationsNavigator, @@ -225,18 +226,7 @@ private fun TaskDetailContent( focusRequester.requestFocus() } - Column( - Modifier - .fillMaxWidth() - .navigationBarsPadding() - .imePadding() - ) { - Spacer(Modifier.height(AppTheme.dimens.spacingMedium)) - - Draggable( - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - + Column(Modifier.fillMaxSize()) { Header( onClose = onClose, onSave = onSave @@ -381,7 +371,7 @@ private fun TaskInput( Box { if (name.text.isEmpty()) { Text( - text = "Add title", + text = stringResource(S.task_detail_task_name_placeholder), modifier = Modifier .fillMaxWidth() .padding(horizontal = AppTheme.dimens.spacingLarge), @@ -397,7 +387,10 @@ private fun TaskInput( .fillMaxWidth() .padding(horizontal = AppTheme.dimens.spacingLarge) .focusRequester(focusRequester), - textStyle = MaterialTheme.typography.headlineSmall, + textStyle = MaterialTheme.typography.headlineSmall.copy( + color = MaterialTheme.colorScheme.onSurface, + ), + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), ) } } diff --git a/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailViewModel.kt b/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailViewModel.kt index 0af482e3..7d38564e 100644 --- a/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailViewModel.kt +++ b/feature/detail/src/main/java/com/atomtasks/feature/detail/TaskDetailViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.atomtasks.feature.detail.EditRecurringTaskResponse.THIS import com.atomtasks.feature.detail.EditRecurringTaskResponse.THIS_AND_FUTURE_ONES -import com.atomtasks.feature.detail.destinations.TaskDetailScreenDestination import com.costular.atomtasks.analytics.AtomAnalytics import com.costular.atomtasks.core.ui.mvi.MviViewModel import com.costular.atomtasks.tasks.model.RecurrenceType @@ -14,6 +13,7 @@ import com.costular.atomtasks.tasks.usecase.AreExactRemindersAvailable import com.costular.atomtasks.tasks.usecase.CreateTaskUseCase import com.costular.atomtasks.tasks.usecase.EditTaskUseCase import com.costular.atomtasks.tasks.usecase.GetTaskByIdUseCase +import com.ramcosta.composedestinations.generated.detail.destinations.TaskDetailScreenDestination import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import java.time.LocalDate diff --git a/feature/edittask/.gitignore b/feature/edittask/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/feature/edittask/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/feature/edittask/build.gradle.kts b/feature/edittask/build.gradle.kts deleted file mode 100644 index 34b8715a..00000000 --- a/feature/edittask/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id("atomtasks.android.feature") - id("atomtasks.android.library") - id("atomtasks.android.library.compose") - id("atomtasks.android.library.ksp") - id("kotlin-android") - kotlin("kapt") - alias(libs.plugins.ksp) - id("atomtasks.detekt") - id("atomtasks.android.library.jacoco") -} - -android { - namespace = "com.costular.atomtasks.feature.edittask" - - ksp { - arg("compose-destinations.mode", "destinations") - arg("compose-destinations.moduleName", "edittask") - } - libraryVariants.all { - kotlin.sourceSets { - getByName(name) { - kotlin.srcDir("build/generated/ksp/$name/kotlin") - } - } - } -} - -dependencies { - implementation(projects.common.tasks) - implementation(libs.compose.destinations) - ksp(libs.compose.destinations.ksp) - - testImplementation(projects.core.testing) - testImplementation(libs.android.junit) - testImplementation(libs.coroutines.test) - testImplementation(libs.truth) - testImplementation(libs.turbine) - testImplementation(libs.mockk) - testImplementation(libs.testparameterinjector) -} diff --git a/feature/edittask/consumer-rules.pro b/feature/edittask/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/feature/edittask/detekt-baseline.xml b/feature/edittask/detekt-baseline.xml deleted file mode 100644 index d2e33528..00000000 --- a/feature/edittask/detekt-baseline.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - InvalidPackageDeclaration:EditTaskScreen.kt$package com.costular.atomtasks.ui.features.edittask - InvalidPackageDeclaration:EditTaskViewModel.kt$package com.costular.atomtasks.ui.features.edittask - - diff --git a/feature/edittask/proguard-rules.pro b/feature/edittask/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/feature/edittask/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/feature/edittask/src/main/AndroidManifest.xml b/feature/edittask/src/main/AndroidManifest.xml deleted file mode 100644 index 8072ee00..00000000 --- a/feature/edittask/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditRecurringTaskConfirmDialog.kt b/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditRecurringTaskConfirmDialog.kt deleted file mode 100644 index 1c3bb4f1..00000000 --- a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditRecurringTaskConfirmDialog.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.costular.atomtasks.feature.edittask - -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.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.AlertDialogDefaults -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -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.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import com.costular.designsystem.theme.AppTheme -import com.costular.designsystem.theme.AtomTheme -import com.costular.atomtasks.core.ui.R.string as S - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun EditRecurringTaskConfirmDialog( - onCancel: () -> Unit, - onEdit: (EditRecurringTaskResponse) -> Unit, -) { - var selected: EditRecurringTaskResponse by remember { - mutableStateOf( - EditRecurringTaskResponse.THIS - ) - } - - BasicAlertDialog( - onDismissRequest = onCancel, - ) { - Surface( - shape = MaterialTheme.shapes.large, - tonalElevation = AlertDialogDefaults.TonalElevation, - ) { - Column(modifier = Modifier.padding(vertical = AppTheme.DialogPadding)) { - Text( - text = stringResource(S.update_recurring_task_dialog_title), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.padding(horizontal = AppTheme.DialogPadding) - ) - - Spacer(Modifier.height(AppTheme.dimens.spacingLarge)) - - SelectableItem(stringResource(S.update_recurring_task_dialog_this), - isSelected = selected == EditRecurringTaskResponse.THIS, - onClick = { - selected = EditRecurringTaskResponse.THIS - } - ) - - SelectableItem(stringResource(S.update_recurring_task_dialog_this_and_future), - isSelected = selected == EditRecurringTaskResponse.THIS_AND_FUTURE_ONES, - onClick = { - selected = EditRecurringTaskResponse.THIS_AND_FUTURE_ONES - } - ) - - Spacer(Modifier.height(AppTheme.dimens.spacingXLarge)) - - DialogButtons( - onCancel = onCancel, - onEdit = { - onEdit(selected) - } - ) - } - } - } - -} - -@Composable -private fun DialogButtons( - onCancel: () -> Unit, - onEdit: () -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(end = AppTheme.DialogPadding), - horizontalArrangement = Arrangement.End, - ) { - TextButton(onClick = onCancel) { - Text( - text = stringResource(S.cancel), - style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Medium), - ) - } - TextButton(onClick = onEdit) { - Text( - text = stringResource(S.accept), - style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Medium), - ) - } - } -} - -@Composable -private fun SelectableItem( - text: String, - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .clickable { onClick() }, - ) { - RadioButton( - modifier = Modifier.padding(start = AppTheme.DialogPadding), - selected = isSelected, - onClick = onClick - ) - - Spacer(Modifier.width(AppTheme.dimens.spacingMedium)) - - Text( - text = text, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .fillMaxWidth() - .padding(end = AppTheme.DialogPadding), - ) - } -} - -enum class EditRecurringTaskResponse { - THIS, - THIS_AND_FUTURE_ONES, -} - -@Preview -@Composable -private fun EditRecurringTaskConfirmDialogPreview() { - AtomTheme { - EditRecurringTaskConfirmDialog( - onCancel = {}, - onEdit = {}, - ) - } -} diff --git a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskScreen.kt b/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskScreen.kt deleted file mode 100644 index 6a83fb5a..00000000 --- a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskScreen.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.costular.atomtasks.feature.edittask - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.costular.atomtasks.core.ui.R -import com.costular.atomtasks.tasks.createtask.CreateTaskExpanded -import com.costular.designsystem.dialogs.AtomSheet -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet - -@OptIn(ExperimentalMaterial3Api::class) -@Destination(style = DestinationStyleBottomSheet::class) -@Composable -fun EditTaskScreen( - taskId: Long, - navigator: DestinationsNavigator, - viewModel: EditTaskViewModel = hiltViewModel(), -) { - val state by viewModel.state.collectAsStateWithLifecycle() - val savingTask = state.savingTask - - LaunchedEffect(savingTask) { - if (savingTask is SavingState.Success) { - navigator.navigateUp() - } - } - - LaunchedEffect(taskId) { - viewModel.loadTask(taskId) - } - - if (state.taskToSave != null) { - EditRecurringTaskConfirmDialog( - onCancel = viewModel::cancelRecurringEdition, - onEdit = viewModel::confirmRecurringEdition, - ) - } - - val task = state.taskState - if (task is TaskState.Success) { - AtomSheet( - title = stringResource(R.string.agenda_edit_task), - contentPadding = 0.dp, - onNavigateUp = { navigator.navigateUp() }, - ) { - CreateTaskExpanded( - value = task.name, - date = task.date, - onSave = { result -> - viewModel.editTask( - name = result.name, - date = result.date, - reminder = result.reminder, - recurrenceType = result.recurrenceType, - recurringUpdateStrategy = null, - ) - }, - reminder = task.reminder, - recurrenceType = task.recurrenceType, - modifier = Modifier - .fillMaxWidth() - .navigationBarsPadding() - .imePadding(), - ) - } - } -} diff --git a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskState.kt b/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskState.kt deleted file mode 100644 index 4eee2c82..00000000 --- a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskState.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.costular.atomtasks.feature.edittask - -import androidx.compose.runtime.Stable -import com.costular.atomtasks.tasks.model.RecurrenceType -import java.time.LocalDate -import java.time.LocalTime - -data class EditTaskState( - val taskState: TaskState = TaskState.Idle, - val savingTask: SavingState = SavingState.Uninitialized, - val taskToSave: TaskToSave? = null, -) { - - companion object { - val Empty = EditTaskState() - } -} - -sealed class TaskState { - - object Idle : TaskState() - - object Loading : TaskState() - - data class Success( - val taskId: Long, - val name: String, - val date: LocalDate, - val reminder: LocalTime?, - val recurrenceType: RecurrenceType?, - ) : TaskState() -} - -@Stable -sealed interface SavingState { - - data object Uninitialized : SavingState - data object Saving : SavingState - data object Failure : SavingState - data object Success : SavingState -} - -data class TaskToSave( - val name: String, - val date: LocalDate, - val reminder: LocalTime?, - val recurrenceType: RecurrenceType?, -) diff --git a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskViewModel.kt b/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskViewModel.kt deleted file mode 100644 index 678fd8ac..00000000 --- a/feature/edittask/src/main/java/com/costular/atomtasks/feature/edittask/EditTaskViewModel.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.costular.atomtasks.feature.edittask - -import androidx.lifecycle.viewModelScope -import com.costular.atomtasks.core.ui.mvi.MviViewModel -import com.costular.atomtasks.feature.edittask.EditRecurringTaskResponse.THIS -import com.costular.atomtasks.feature.edittask.EditRecurringTaskResponse.THIS_AND_FUTURE_ONES -import com.costular.atomtasks.tasks.model.RecurrenceType -import com.costular.atomtasks.tasks.model.RecurringUpdateStrategy -import com.costular.atomtasks.tasks.usecase.EditTaskUseCase -import com.costular.atomtasks.tasks.usecase.GetTaskByIdUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import java.time.LocalDate -import java.time.LocalTime -import javax.inject.Inject -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch - -@HiltViewModel -class EditTaskViewModel @Inject constructor( - private val getTaskByIdUseCase: GetTaskByIdUseCase, - private val editTaskUseCase: EditTaskUseCase, -) : MviViewModel(EditTaskState.Empty) { - - fun loadTask(taskId: Long) { - viewModelScope.launch { - getTaskByIdUseCase(GetTaskByIdUseCase.Params(taskId)) - .onStart { - setState { copy(taskState = TaskState.Loading) } - } - .collect { task -> - setState { - copy( - taskState = TaskState.Success( - taskId = task.id, - name = task.name, - date = task.day, - reminder = task.reminder?.time, - recurrenceType = task.recurrenceType, - ), - ) - } - } - } - } - - fun cancelRecurringEdition() { - setState { copy(taskToSave = null) } - } - - fun confirmRecurringEdition(response: EditRecurringTaskResponse) { - val taskToSave = state.value.taskToSave - requireNotNull(taskToSave) { - "taskToSave must not be null" - } - - val recurringUpdateStrategy = when (response) { - THIS -> RecurringUpdateStrategy.SINGLE - THIS_AND_FUTURE_ONES -> RecurringUpdateStrategy.SINGLE_AND_FUTURE - } - - editTask( - name = taskToSave.name, - date = taskToSave.date, - reminder = taskToSave.reminder, - recurrenceType = taskToSave.recurrenceType, - recurringUpdateStrategy = recurringUpdateStrategy, - ) - } - - fun editTask( - name: String, - date: LocalDate, - reminder: LocalTime?, - recurrenceType: RecurrenceType?, - recurringUpdateStrategy: RecurringUpdateStrategy?, - ) { - viewModelScope.launch { - val state = state.value - if (state.taskState !is TaskState.Success) { - return@launch - } - - if (state.taskState.recurrenceType != null && recurringUpdateStrategy == null) { - setState { copy(taskToSave = TaskToSave(name, date, reminder, recurrenceType)) } - return@launch - } - - editTaskUseCase( - EditTaskUseCase.Params( - taskId = state.taskState.taskId, - name = name, - date = date, - reminderTime = reminder, - recurrenceType = recurrenceType, - recurringUpdateStrategy = recurringUpdateStrategy, - ), - ).fold( - ifError = { - setState { copy(savingTask = SavingState.Failure) } - }, - ifResult = { - setState { copy(savingTask = SavingState.Success) } - } - ) - } - } -} diff --git a/feature/edittask/src/test/java/com/costular/atomtasks/edittask/EditTaskViewModelTest.kt b/feature/edittask/src/test/java/com/costular/atomtasks/edittask/EditTaskViewModelTest.kt deleted file mode 100644 index 32d49fa2..00000000 --- a/feature/edittask/src/test/java/com/costular/atomtasks/edittask/EditTaskViewModelTest.kt +++ /dev/null @@ -1,218 +0,0 @@ -package com.costular.atomtasks.edittask - -import app.cash.turbine.test -import com.costular.atomtasks.core.Either -import com.costular.atomtasks.core.testing.MviViewModelTest -import com.costular.atomtasks.core.toError -import com.costular.atomtasks.feature.edittask.EditRecurringTaskResponse.THIS -import com.costular.atomtasks.feature.edittask.EditTaskViewModel -import com.costular.atomtasks.feature.edittask.SavingState -import com.costular.atomtasks.feature.edittask.TaskState -import com.costular.atomtasks.tasks.fake.TaskRecurring -import com.costular.atomtasks.tasks.fake.TaskToday -import com.costular.atomtasks.tasks.model.RecurringUpdateStrategy -import com.costular.atomtasks.tasks.model.UpdateTaskUseCaseError -import com.costular.atomtasks.tasks.usecase.EditTaskUseCase -import com.costular.atomtasks.tasks.usecase.GetTaskByIdUseCase -import com.google.common.truth.Truth.assertThat -import com.google.testing.junit.testparameterinjector.TestParameterInjector -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import java.time.LocalDate -import java.time.LocalTime -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(TestParameterInjector::class) -class EditTaskViewModelTest : MviViewModelTest() { - - lateinit var sut: EditTaskViewModel - - private val getTaskByIdUseCase: GetTaskByIdUseCase = mockk(relaxed = true) - private val editTaskUseCase: EditTaskUseCase = mockk(relaxed = true) - - @Before - fun setUp() { - sut = EditTaskViewModel( - getTaskByIdUseCase = getTaskByIdUseCase, - editTaskUseCase = editTaskUseCase, - ) - } - - @Test - fun `should load task successfully`() = runTest { - givenTask() - - sut.loadTask(TaskToday.id) - - sut.state.test { - assertThat( - (expectMostRecentItem().taskState as TaskState.Success).taskId, - ).isEqualTo(TaskToday.id) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `should emit idle when state is created`() = runTest { - sut.state.test { - assertThat(awaitItem().taskState).isInstanceOf(TaskState.Idle::class.java) - } - } - - @Test - fun `should not be able to update if task has not been loaded`() = runTest { - sut.editTask( - name = "whatever", - date = LocalDate.now(), - reminder = null, - recurrenceType = null, - recurringUpdateStrategy = null, - ) - - coVerify(exactly = 0) { editTaskUseCase(any()) } - } - - @Test - fun `should emit success when edit task succeeded`() = runTest { - givenTask() - coEvery { editTaskUseCase.invoke(any()) } returns Either.Result(Unit) - - val newTask = "whatever" - val newDate = LocalDate.now().plusDays(1) - val newReminder = LocalTime.of(10, 0) - - sut.loadTask(TaskToday.id) - sut.editTask( - name = newTask, - date = newDate, - reminder = newReminder, - recurrenceType = null, - recurringUpdateStrategy = null, - ) - - sut.state.test { - assertThat(expectMostRecentItem().savingTask).isInstanceOf(SavingState.Success::class.java) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `should emit error when edit task fails`() = runTest { - givenTask() - coEvery { - editTaskUseCase.invoke(any()) - } returns UpdateTaskUseCaseError.UnknownError.toError() - - val newTask = "whatever" - val newDate = LocalDate.now().plusDays(1) - val newReminder = LocalTime.of(10, 0) - - sut.loadTask(TaskToday.id) - sut.editTask( - name = newTask, - date = newDate, - reminder = newReminder, - recurrenceType = null, - recurringUpdateStrategy = null, - ) - - assertThat(sut.state.value.savingTask).isInstanceOf(SavingState.Failure::class.java) - } - - @Test - fun `should set taskToSave successfully when update recurring task`() = runTest { - givenRecurringTask() - - val newName = "whatever" - val newDate = LocalDate.now().plusDays(1) - val newReminder = LocalTime.of(10, 0) - - sut.loadTask(TaskRecurring.id) - - sut.editTask( - name = newName, - date = newDate, - reminder = newReminder, - recurrenceType = null, - recurringUpdateStrategy = null, - ) - - assertThat(sut.state.value.taskToSave?.date).isEqualTo(newDate) - assertThat(sut.state.value.taskToSave?.name).isEqualTo(newName) - assertThat(sut.state.value.taskToSave?.reminder).isEqualTo(newReminder) - } - - @Test - fun `should set taskToSave to null when cancel edition given task is recurring`() = runTest { - givenRecurringTask() - - val newName = "whatever" - val newDate = LocalDate.now().plusDays(1) - val newReminder = LocalTime.of(10, 0) - - sut.loadTask(TaskToday.id) - - sut.editTask( - name = newName, - date = newDate, - reminder = newReminder, - recurrenceType = null, - recurringUpdateStrategy = null, - ) - sut.cancelRecurringEdition() - - assertThat(sut.state.value.taskToSave).isNull() - } - - @Test - fun `should edit task properly when confirm update recurring`() = runTest { - givenRecurringTask() - givenSuccessfulEditTaskUseCase() - - val newName = "whatever" - val newDate = LocalDate.now().plusDays(1) - val newReminder = LocalTime.of(10, 0) - - sut.loadTask(TaskRecurring.id) - - sut.editTask( - name = newName, - date = newDate, - reminder = newReminder, - recurrenceType = null, - recurringUpdateStrategy = null, - ) - - sut.confirmRecurringEdition(THIS) - - coVerify { - editTaskUseCase( - EditTaskUseCase.Params( - TaskRecurring.id, - newName, - newDate, - newReminder, - null, - RecurringUpdateStrategy.SINGLE, - ) - ) - } - } - - private fun givenSuccessfulEditTaskUseCase() { - coEvery { editTaskUseCase.invoke(any()) } returns Either.Result(Unit) - } - - private fun givenTask() { - coEvery { getTaskByIdUseCase.invoke(any()) } returns flowOf(TaskToday) - } - - private fun givenRecurringTask() { - coEvery { getTaskByIdUseCase.invoke(any()) } returns flowOf(TaskRecurring) - } -} diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 5509c2e7..b72fb3e2 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -4,18 +4,16 @@ plugins { id("atomtasks.android.library.compose") id("atomtasks.android.library.ksp") id("kotlin-android") - kotlin("kapt") alias(libs.plugins.ksp) id("atomtasks.detekt") id("atomtasks.android.library.jacoco") - id("dagger.hilt.android.plugin") + id("atomtasks.android.hilt") } android { namespace = "com.costular.atomtasks.feature.settings" ksp { - arg("compose-destinations.mode", "destinations") arg("compose-destinations.moduleName", "settings") } } @@ -23,8 +21,7 @@ android { dependencies { implementation(projects.core.analytics) implementation(project(mapOf("path" to ":feature:agenda"))) - kapt(libs.hilt.compiler) - implementation(libs.compose.destinations) + ksp(libs.hilt.compiler) ksp(libs.compose.destinations.ksp) implementation(libs.compose.ui.manifest) implementation(libs.calendar) diff --git a/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsGraph.kt b/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsGraph.kt new file mode 100644 index 00000000..a5a2dd88 --- /dev/null +++ b/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsGraph.kt @@ -0,0 +1,7 @@ +package com.costular.atomtasks.settings + +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph +import com.ramcosta.composedestinations.annotation.NavGraph + +@NavGraph +internal annotation class SettingsGraph diff --git a/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsScreen.kt index 618d9223..ed5f762d 100644 --- a/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/costular/atomtasks/settings/SettingsScreen.kt @@ -27,11 +27,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.costular.atomtasks.core.ui.R import com.costular.atomtasks.data.settings.Theme -import com.costular.atomtasks.settings.destinations.ThemeSelectorScreenDestination import com.costular.designsystem.components.AtomTopBar import com.costular.designsystem.theme.AppTheme import com.costular.designsystem.theme.AtomTheme import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.generated.settings.destinations.ThemeSelectorScreenDestination import com.ramcosta.composedestinations.result.ResultRecipient import com.ramcosta.composedestinations.result.getOr import org.jetbrains.annotations.VisibleForTesting @@ -46,7 +46,9 @@ object EmptySettingsNavigator : SettingsNavigator { override fun navigateToSelectTheme(theme: String) = Unit } -@Destination +@Destination( + start = true, +) @Composable fun SettingsScreen( navigator: SettingsNavigator, diff --git a/feature/settings/src/main/java/com/costular/atomtasks/settings/ThemeSelectorDialog.kt b/feature/settings/src/main/java/com/costular/atomtasks/settings/ThemeSelectorDialog.kt index f8c1dae4..1f7e2e18 100644 --- a/feature/settings/src/main/java/com/costular/atomtasks/settings/ThemeSelectorDialog.kt +++ b/feature/settings/src/main/java/com/costular/atomtasks/settings/ThemeSelectorDialog.kt @@ -32,9 +32,9 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import com.ramcosta.composedestinations.result.EmptyResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator import com.costular.atomtasks.core.ui.R -import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet +import com.ramcosta.composedestinations.bottomsheet.spec.DestinationStyleBottomSheet -@Destination(style = DestinationStyleBottomSheet::class) +@Destination(style = DestinationStyleBottomSheet::class) @Composable fun ThemeSelectorScreen( selectedTheme: String, diff --git a/gradle.properties b/gradle.properties index 7f888195..39b5ce3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,6 @@ org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true -kapt.incremental.apt=true android.useAndroidX=true kotlin.code.style=official org.gradle.unsafe.configuration-cache=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e7e0bba..54a35a37 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,34 +1,35 @@ [versions] -kotlin = "2.0.0" -coroutines = "1.8.1" -agp = "8.5.0" +kotlin = "2.0.20" +coroutines = "1.9.0" +agp = "8.5.2" core = "1.13.1" appCompat = "1.7.0" -lifecycle = "2.8.2" -viewModelCompose = "2.8.2" +lifecycle = "2.8.6" +viewModelCompose = "2.8.6" mockk = "1.13.9" hilt = "2.51.1" hiltAndroidx = "1.2.0" testRunner = "1.6.0" testJetpack = "2.2.0" -composeMaterial3 = "1.3.0-beta04" +composeMaterial3 = "1.3.0" accompanist = "0.34.0" -workManager = "2.9.0" +workManager = "2.9.1" room = "2.6.1" paparazzi = "1.3.4" -composeDestinations = "1.11.4-alpha" +composeDestinations = "2.1.0-beta12" junit5 = "5.9.0" firebaseCrashlyticsPlugin = "3.0.2" -ksp = "2.0.0-1.0.21" +ksp = "2.0.20-1.0.25" jacoco = "0.8.7" -androidxComposeBom = "2024.06.00" +androidxComposeBom = "2024.09.03" +chrisBanesComposeBom = "2023.04.00-beta02" reordeable = "0.9.6" collectionsImmutable = "0.3.7" junit = "4.13.2" -androidx-test-ext-junit = "1.2.0" -espresso-core = "3.6.0" +androidx-test-ext-junit = "1.2.1" +espresso-core = "3.6.1" benchmarkBaselineProfileGradlePlugin = "1.2.4" uiautomator = "2.3.0" benchmarkMacroJunit4 = "1.2.4" @@ -43,6 +44,7 @@ androidx-core = { module = "androidx.core:core-ktx", version.ref = "core" } viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "viewModelCompose" } lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } +hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltAndroidx" } hilt-navigation-compose = "androidx.hilt:hilt-navigation-compose:1.2.0" @@ -52,35 +54,36 @@ startup = "androidx.startup:startup-runtime:1.1.1" preferences = "androidx.preference:preference-ktx:1.2.1" preferences-datastore = "androidx.datastore:datastore-preferences:1.1.1" compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } -compose-activity = "androidx.activity:activity-compose:1.9.0" +compose-bom-alpha = { module = "dev.chrisbanes.compose:compose-bom", version.ref = "chrisBanesComposeBom" } +compose-activity = "androidx.activity:activity-compose:1.9.2" compose-ui = { module = "androidx.compose.ui:ui" } compose-foundation = { module = "androidx.compose.foundation:foundation" } compose-layout = { module = "androidx.compose.foundation:foundation-layout" } compose-runtime = { module = "androidx.compose.runtime:runtime" } -compose-material-icons = { module = "androidx.compose.material:material-icons-extended", version = "1.7.0-beta05" } +compose-material-icons = { module = "androidx.compose.material:material-icons-extended", version = "1.7.3" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4" } compose-ui-manifest = { module = "androidx.compose.ui:ui-test-manifest" } compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "composeMaterial3" } -compose-material3-windowsize = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "composeMaterial3" } +compose-adaptative-navigation = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" } +compose-material3-windowsize = { module = "androidx.compose.material3:material3-window-size-class" } compose-ui-text-fonts = { module = "androidx.compose.ui:ui-text-google-fonts" } -accompanist-systemui = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" } accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" } work = { module = "androidx.work:work-runtime-ktx", version.ref = "workManager" } work-testing = { module = "androidx.work:work-testing", version.ref = "workManager" } -fragment = "androidx.fragment:fragment-ktx:1.8.0" room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" } calendar = "com.kizitonwose.calendar:compose:2.6.0-beta03" -android-junit = "androidx.test.ext:junit-ktx:1.2.0" -firebase-bom = "com.google.firebase:firebase-bom:33.1.1" +android-junit = "androidx.test.ext:junit-ktx:1.2.1" +firebase-bom = "com.google.firebase:firebase-bom:33.4.0" firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" } firebase-config = { module = "com.google.firebase:firebase-config" } -compose-destinations = { module = "io.github.raamcosta.compose-destinations:animations-core", version.ref = "composeDestinations" } +compose-destinations-core = { module = "io.github.raamcosta.compose-destinations:core", version.ref = "composeDestinations" } compose-destinations-ksp = { module = "io.github.raamcosta.compose-destinations:ksp", version.ref = "composeDestinations" } +compose-destinations-bottomsheet = { module = "io.github.raamcosta.compose-destinations:bottom-sheet", version.ref = "composeDestinations" } reordeable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "reordeable" } play-review = { group = "com.google.android.play", name = "review-ktx", version.ref = "playReview" } @@ -94,7 +97,7 @@ androidx-test = { module = "androidx.arch.core:core-testing", version.ref = "tes turbine = "app.cash.turbine:turbine:1.1.0" androidx-test-runner = { module = "androidx.test:runner", version.ref = "testRunner" } androidx-test-rules = { module = "androidx.test:rules", version.ref = "testRunner" } -android-desugarjdk = "com.android.tools:desugar_jdk_libs:2.0.4" +android-desugarjdk = "com.android.tools:desugar_jdk_libs:2.1.2" robolectric = "org.robolectric:robolectric:4.11.1" testparameterinjector = "com.google.testparameterinjector:test-parameter-injector:1.12" kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collectionsImmutable" } diff --git a/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/OutlinedTextFieldSnapshotTest.kt b/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/OutlinedTextFieldSnapshotTest.kt deleted file mode 100644 index 7e5710b0..00000000 --- a/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/OutlinedTextFieldSnapshotTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.costular.atomtasks.screenshottesting.designsystem - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.ui.Modifier -import app.cash.paparazzi.Paparazzi -import com.costular.atomtasks.screenshottesting.utils.FontSize -import com.costular.atomtasks.screenshottesting.utils.PaparazziFactory -import com.costular.atomtasks.screenshottesting.utils.Theme -import com.costular.atomtasks.screenshottesting.utils.asFloat -import com.costular.atomtasks.screenshottesting.utils.isDarkTheme -import com.costular.atomtasks.screenshottesting.utils.screenshot -import com.costular.designsystem.components.AtomOutlinedTextField -import com.google.testing.junit.testparameterinjector.TestParameter -import com.google.testing.junit.testparameterinjector.TestParameterInjector -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(TestParameterInjector::class) -class OutlinedTextFieldSnapshotTest { - - @TestParameter - private lateinit var fontScale: FontSize - - @TestParameter - private lateinit var themeMode: Theme - - @get:Rule - val paparazzi: Paparazzi = PaparazziFactory.create() - - @Test - fun outlinedTextField() { - paparazzi.screenshot( - isDarkTheme = themeMode.isDarkTheme(), - fontScale = fontScale.asFloat(), - ) { - AtomOutlinedTextField( - value = "Input text...", - onValueChange = {}, - modifier = Modifier.fillMaxWidth(), - ) - } - } - - @Test - fun emptyOutlinedTextField() { - paparazzi.screenshot( - isDarkTheme = themeMode.isDarkTheme(), - fontScale = fontScale.asFloat(), - ) { - AtomOutlinedTextField( - value = "", - onValueChange = {}, - modifier = Modifier.fillMaxWidth(), - ) - } - } -} diff --git a/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskCardScreenshotTest.kt b/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskCardScreenshotTest.kt index 58366315..421675cd 100644 --- a/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskCardScreenshotTest.kt +++ b/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskCardScreenshotTest.kt @@ -41,9 +41,10 @@ class TaskCardScreenshotTest { isFinished = false, reminder = null, onMark = {}, - onOpen = {}, + onClick = {}, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } } @@ -59,9 +60,10 @@ class TaskCardScreenshotTest { isFinished = true, reminder = null, onMark = {}, - onOpen = {}, + onClick = {}, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } } @@ -81,9 +83,10 @@ class TaskCardScreenshotTest { LocalDate.now(), ), onMark = { }, - onOpen = { }, + onClick = { }, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } } @@ -99,9 +102,10 @@ class TaskCardScreenshotTest { isFinished = true, reminder = null, onMark = { }, - onOpen = { }, + onClick = { }, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } } @@ -117,9 +121,10 @@ class TaskCardScreenshotTest { isFinished = false, reminder = null, onMark = { }, - onOpen = { }, + onClick = { }, isBeingDragged = false, recurrenceType = RecurrenceType.WEEKLY, + onClickMore = {}, ) } } @@ -139,9 +144,10 @@ class TaskCardScreenshotTest { date = LocalDate.now(), ), onMark = { }, - onOpen = { }, + onClick = { }, isBeingDragged = false, recurrenceType = RecurrenceType.DAILY, + onClickMore = {}, ) } } @@ -162,9 +168,10 @@ class TaskCardScreenshotTest { LocalDate.now(), ), onMark = { }, - onOpen = { }, + onClick = { }, isBeingDragged = false, recurrenceType = null, + onClickMore = {}, ) } } diff --git a/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskListSnapshotTest.kt b/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskListSnapshotTest.kt index 42c37456..ade31a84 100644 --- a/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskListSnapshotTest.kt +++ b/screenshot_testing/src/test/java/com/costular/atomtasks/screenshottesting/designsystem/TaskListSnapshotTest.kt @@ -1,7 +1,6 @@ package com.costular.atomtasks.screenshottesting.designsystem import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -104,16 +103,17 @@ class TaskListSnapshotTest { } } - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun GeneratedTaskList(tasks: List) { Scaffold { - com.costular.atomtasks.core.ui.tasks.TaskList( + TaskList( tasks = tasks, onClick = {}, onMarkTask = { _, _ -> }, modifier = Modifier.fillMaxWidth(), state = rememberReorderableLazyListState(onMove = { _, _ -> }), + onClickMore = {}, + onDeleteTask = {}, ) } } diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,DARK].png index e1779e9c..79a006b4 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,LIGHT].png index a27bc927..1ec855ff 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,DARK].png index 41a0847d..5f9fadf1 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,LIGHT].png index 6d02608d..17739497 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskDoneTest[NORMAL,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,DARK].png index bca0dada..06543fc8 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,LIGHT].png index 8282f762..73b331a8 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,DARK].png index e453c607..76ad2a7d 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,LIGHT].png index 88deac4b..d6d7121f 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinishedWithLongName[NORMAL,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,DARK].png index 1e5eeab6..f85ae272 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,LIGHT].png index b6cbe2da..8477ba04 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,DARK].png index 74d98060..24e329e8 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,LIGHT].png index 84170b8c..8372ced6 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskFinished[NORMAL,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,DARK].png index 8f844127..244a47d5 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,LIGHT].png index 5ac2361a..f554babb 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,DARK].png index c0ee56ef..3b60c1b4 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,LIGHT].png index 9fca2385..419cdbd7 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurringWithReminder[NORMAL,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,DARK].png index cbbf4e59..dae15a69 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,LIGHT].png index a0fc1f1f..ae35086f 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,DARK].png index 56c8d5cb..0b9ffa00 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,LIGHT].png index 4f5858ee..2a717c88 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskRecurring[NORMAL,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,DARK].png index 9b6d2747..4b902be4 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,LIGHT].png index 4866f606..8eea86e9 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,DARK].png index 8e69b3cb..0d109cf7 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,LIGHT].png index 9064eca0..c4e02153 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskTest[NORMAL,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,DARK].png index 6098781a..85bb935a 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,LIGHT].png index d3cc4cb1..120bf99a 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[BIG,LIGHT].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,DARK].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,DARK].png index f107b9d7..8ce451c1 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,DARK].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,DARK].png differ diff --git a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,LIGHT].png b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,LIGHT].png index a4668da6..55226631 100644 Binary files a/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,LIGHT].png and b/screenshot_testing/src/test/snapshots/images/com.costular.atomtasks.screenshottesting.designsystem_TaskCardScreenshotTest_taskWithReminder[NORMAL,LIGHT].png differ diff --git a/settings.gradle.kts b/settings.gradle.kts index 0fa26c20..cf69d051 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,8 +27,6 @@ include(":common:tasks") include(":data") include(":feature:settings") include(":feature:agenda") -include(":feature:createtask") -include(":feature:edittask") include(":screenshot_testing") include(":core:analytics") include(":feature:postpone-task")