From fe2fdd6ef2620f941b0327c4ccc178e64b4fb89a Mon Sep 17 00:00:00 2001 From: raouf-kamel Date: Fri, 4 Jul 2025 17:32:23 +0300 Subject: [PATCH 1/6] add animation Extensions --- .../utils/animation/AnimationExtensions.kt | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt new file mode 100644 index 00000000..81c6e554 --- /dev/null +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt @@ -0,0 +1,104 @@ +package com.sanaa.tudee_assistant.presentation.utils.animation + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay + +enum class SlideDirection { + Up, Down, End, Start, Left, Right +} + +fun Modifier.applyTaskCardAnimation( + delayMillis: Int, + durationMillis: Int = 600, + distance: Dp = 10.dp +): Modifier = this + .fadeAnimation(durationMillis = 400) + .slide( + direction = SlideDirection.Up, + delayMillis = delayMillis, + durationMillis = durationMillis, + distance = distance + ) + + +fun Modifier.slide( + direction: SlideDirection, + distance: Dp = 10.dp, + durationMillis: Int = 550, + delayMillis: Int = 0, + animationSpec: AnimationSpec = tween(durationMillis = durationMillis) +): Modifier = composed { + val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl + var isAnimated by remember { mutableStateOf(false) } + val offsetX by animateFloatAsState( + targetValue = if (isAnimated) { + 0f + } else { + when (direction) { + SlideDirection.Start -> if (isRtl) distance.value else -distance.value + SlideDirection.End -> if (isRtl) -distance.value else distance.value + SlideDirection.Left -> distance.value + SlideDirection.Right -> -distance.value + else -> 0f + } + }, + animationSpec = animationSpec, + label = "swipeAnimationX" + ) + val offsetY by animateFloatAsState( + targetValue = if (isAnimated) { + 0f + } else { + when (direction) { + SlideDirection.Up -> distance.value + SlideDirection.Down -> -distance.value + else -> 0f + } + }, + animationSpec = animationSpec, + label = "swipeAnimationY" + ) + + LaunchedEffect(key1 = Unit) { + delay(delayMillis.toLong()) + isAnimated = true + } + + this.offset(x = offsetX.dp, y = offsetY.dp) +} + +fun Modifier.fadeAnimation( + from: Float = 0f, + to: Float = 1f, + durationMillis: Int = 400 +): Modifier = composed { + var isAnimated by remember { mutableStateOf(false) } + val alpha by animateFloatAsState( + targetValue = if (isAnimated) to else from, + animationSpec = tween(durationMillis = durationMillis), + label = "fadeAnimation" + ) + + LaunchedEffect(Unit) { + isAnimated = true + } + + graphicsLayer { + this.alpha = alpha + } +} \ No newline at end of file From e013e837326ef53a6007cf82bcab74c78586d550 Mon Sep 17 00:00:00 2001 From: raouf-kamel Date: Fri, 4 Jul 2025 17:33:27 +0300 Subject: [PATCH 2/6] add animation to CategoryScreen --- .../presentation/screen/category/CategoryScreen.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt index 35ab5709..6e5b4ed3 100644 --- a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -27,6 +27,8 @@ import com.sanaa.tudee_assistant.presentation.designSystem.theme.Theme import com.sanaa.tudee_assistant.presentation.navigation.AppNavigation import com.sanaa.tudee_assistant.presentation.navigation.CategoryTasksScreenRoute import com.sanaa.tudee_assistant.presentation.shared.LocalSnackBarState +import com.sanaa.tudee_assistant.presentation.utils.animation.SlideDirection +import com.sanaa.tudee_assistant.presentation.utils.animation.slide import kotlinx.coroutines.flow.collectLatest import org.koin.androidx.compose.koinViewModel @@ -102,8 +104,13 @@ fun CategoryScreenContent( verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - items(state.allCategories) { category -> + itemsIndexed(state.allCategories) { index, category -> CategoryItem( + modifier = Modifier + .slide( + direction = SlideDirection.Up, + delayMillis = (index * 100 / (index + 2)).coerceAtMost(1500) + ), category = category, topContent = { CategoryCount(category.tasksCount.toString()) }, onClick = { From f5a8a951a39ca536e371b44b9d33e8466f2dd3f6 Mon Sep 17 00:00:00 2001 From: raouf-kamel Date: Fri, 4 Jul 2025 17:34:15 +0300 Subject: [PATCH 3/6] add animation to TaskCountByStatusCard --- .../screen/home/component/HomeOverviewCard.kt | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt index b7578807..b8a3f320 100644 --- a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt @@ -17,6 +17,7 @@ import com.sanaa.tudee_assistant.presentation.designSystem.component.Slider import com.sanaa.tudee_assistant.presentation.designSystem.theme.Theme import com.sanaa.tudee_assistant.presentation.model.TaskUiStatus import com.sanaa.tudee_assistant.presentation.screen.home.HomeScreenUiState +import com.sanaa.tudee_assistant.presentation.utils.animation.applyTaskCardAnimation @Composable fun HomeOverviewCard(state: HomeScreenUiState) { @@ -44,27 +45,36 @@ fun HomeOverviewCard(state: HomeScreenUiState) { color = Theme.color.title, style = Theme.textStyle.title.large ) + TaskStatusCardsRow(state) + } +} + +@Composable +fun TaskStatusCardsRow(state: HomeScreenUiState) { + val statuses = listOf( + TaskUiStatus.DONE, + TaskUiStatus.IN_PROGRESS, + TaskUiStatus.TODO + ) + + Row( + modifier = Modifier.padding( + top = Theme.dimension.small, + start = Theme.dimension.regular, + end = Theme.dimension.regular + ), + horizontalArrangement = Arrangement.spacedBy(Theme.dimension.small) + ) { + statuses.forEachIndexed { index, status -> + val count = state.tasks.count { it.status == status } - Row( - modifier = Modifier.padding( - top = Theme.dimension.small, - start = Theme.dimension.regular, - end = Theme.dimension.regular - ), - horizontalArrangement = Arrangement.spacedBy(Theme.dimension.small) - ) { - TaskCountByStatusCard( - count = state.tasks.filter { it.status == TaskUiStatus.DONE }.size, - taskUiStatus = TaskUiStatus.DONE, - ) - TaskCountByStatusCard( - count = state.tasks.filter { it.status == TaskUiStatus.IN_PROGRESS }.size, - taskUiStatus = TaskUiStatus.IN_PROGRESS, - ) TaskCountByStatusCard( - count = state.tasks.filter { it.status == TaskUiStatus.TODO }.size, - taskUiStatus = TaskUiStatus.TODO, + count = count, + taskUiStatus = status, + modifier = Modifier + .weight(1f) + .applyTaskCardAnimation(delayMillis = index * 50) ) } } -} +} \ No newline at end of file From 39d02a8eee6badce60adf65db674ed2e3ddb8ef2 Mon Sep 17 00:00:00 2001 From: raouf-kamel Date: Fri, 4 Jul 2025 17:35:21 +0300 Subject: [PATCH 4/6] refactor TaskCountByStatusCard --- .../screen/home/component/TaskCountByStatusCard.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/TaskCountByStatusCard.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/TaskCountByStatusCard.kt index d247f97c..e6d8f525 100644 --- a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/TaskCountByStatusCard.kt +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/TaskCountByStatusCard.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset @@ -36,7 +35,7 @@ import com.sanaa.tudee_assistant.presentation.designSystem.theme.TudeeTheme import com.sanaa.tudee_assistant.presentation.model.TaskUiStatus @Composable -fun RowScope.TaskCountByStatusCard( +fun TaskCountByStatusCard( taskUiStatus: TaskUiStatus, count: Int, modifier: Modifier = Modifier, @@ -48,7 +47,6 @@ fun RowScope.TaskCountByStatusCard( Box( modifier - .weight(1f) .clip(RoundedCornerShape(20.dp)) .clipToBounds() .background(backgroundColor) @@ -171,9 +169,9 @@ private fun Preview() { .padding(Theme.dimension.medium), horizontalArrangement = Arrangement.spacedBy(Theme.dimension.medium) ) { - TaskCountByStatusCard(TaskUiStatus.DONE, 2) - TaskCountByStatusCard(TaskUiStatus.IN_PROGRESS, 16) - TaskCountByStatusCard(TaskUiStatus.TODO, 1) + TaskCountByStatusCard(TaskUiStatus.DONE, 2, Modifier.weight(1f)) + TaskCountByStatusCard(TaskUiStatus.IN_PROGRESS, 16, Modifier.weight(1f)) + TaskCountByStatusCard(TaskUiStatus.TODO, 1, Modifier.weight(1f)) } } } \ No newline at end of file From a8a1ab3e4dad4f6ce9169db9e5c6a3e3df5e626e Mon Sep 17 00:00:00 2001 From: raouf-kamel Date: Fri, 4 Jul 2025 18:08:16 +0300 Subject: [PATCH 5/6] move animation extensions to package modifier --- .../{utils/animation => modifire}/AnimationExtensions.kt | 2 +- .../presentation/screen/category/CategoryScreen.kt | 4 ++-- .../presentation/screen/home/component/HomeOverviewCard.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename app/src/main/java/com/sanaa/tudee_assistant/presentation/{utils/animation => modifire}/AnimationExtensions.kt (98%) diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationExtensions.kt similarity index 98% rename from app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt rename to app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationExtensions.kt index 81c6e554..fe7c7dcb 100644 --- a/app/src/main/java/com/sanaa/tudee_assistant/presentation/utils/animation/AnimationExtensions.kt +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationExtensions.kt @@ -1,4 +1,4 @@ -package com.sanaa.tudee_assistant.presentation.utils.animation +package com.sanaa.tudee_assistant.presentation.modifire import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateFloatAsState diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt index 6e5b4ed3..26de4149 100644 --- a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/category/CategoryScreen.kt @@ -24,11 +24,11 @@ import com.sanaa.tudee_assistant.presentation.designSystem.component.CategoryCou import com.sanaa.tudee_assistant.presentation.designSystem.component.CategoryItem import com.sanaa.tudee_assistant.presentation.designSystem.component.button.FloatingActionButton import com.sanaa.tudee_assistant.presentation.designSystem.theme.Theme +import com.sanaa.tudee_assistant.presentation.modifire.SlideDirection +import com.sanaa.tudee_assistant.presentation.modifire.slide import com.sanaa.tudee_assistant.presentation.navigation.AppNavigation import com.sanaa.tudee_assistant.presentation.navigation.CategoryTasksScreenRoute import com.sanaa.tudee_assistant.presentation.shared.LocalSnackBarState -import com.sanaa.tudee_assistant.presentation.utils.animation.SlideDirection -import com.sanaa.tudee_assistant.presentation.utils.animation.slide import kotlinx.coroutines.flow.collectLatest import org.koin.androidx.compose.koinViewModel diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt index b8a3f320..f0764e53 100644 --- a/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt +++ b/app/src/main/java/com/sanaa/tudee_assistant/presentation/screen/home/component/HomeOverviewCard.kt @@ -16,8 +16,8 @@ import com.sanaa.tudee_assistant.R import com.sanaa.tudee_assistant.presentation.designSystem.component.Slider import com.sanaa.tudee_assistant.presentation.designSystem.theme.Theme import com.sanaa.tudee_assistant.presentation.model.TaskUiStatus +import com.sanaa.tudee_assistant.presentation.modifire.applyTaskCardAnimation import com.sanaa.tudee_assistant.presentation.screen.home.HomeScreenUiState -import com.sanaa.tudee_assistant.presentation.utils.animation.applyTaskCardAnimation @Composable fun HomeOverviewCard(state: HomeScreenUiState) { From 78bb4c80001e5982d2e7db454ed387f0bafbe98a Mon Sep 17 00:00:00 2001 From: raouf-kamel Date: Fri, 4 Jul 2025 18:48:56 +0300 Subject: [PATCH 6/6] rename to Animation Modifiers --- .../modifire/{AnimationExtensions.kt => AnimationModifiers.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/{AnimationExtensions.kt => AnimationModifiers.kt} (100%) diff --git a/app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationExtensions.kt b/app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationModifiers.kt similarity index 100% rename from app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationExtensions.kt rename to app/src/main/java/com/sanaa/tudee_assistant/presentation/modifire/AnimationModifiers.kt