From 435c07bf01edd9c4796d187e95f3ce31f048bf0d Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Tue, 13 Jan 2026 18:09:15 +0500 Subject: [PATCH] feat: Enhance screen transition animations and preserve state during navigation for improved user experience --- .../com/rosetta/messenger/MainActivity.kt | 131 ++++++++++++++++-- .../messenger/ui/chats/ChatsListScreen.kt | 7 +- 2 files changed, 122 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/MainActivity.kt b/app/src/main/java/com/rosetta/messenger/MainActivity.kt index 9510fb6..8958b90 100644 --- a/app/src/main/java/com/rosetta/messenger/MainActivity.kt +++ b/app/src/main/java/com/rosetta/messenger/MainActivity.kt @@ -6,6 +6,8 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.animation.* import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -19,6 +21,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.rosetta.messenger.data.AccountManager @@ -231,22 +234,124 @@ fun MainScreen( var selectedUser by remember { mutableStateOf(null) } var showSearchScreen by remember { mutableStateOf(false) } - // Анимированный переход между экранами + // Анимированный переход между экранами - Telegram-style AnimatedContent( targetState = Triple(selectedUser, showSearchScreen, Unit), transitionSpec = { - // Только плавный fade без смещения - чтобы header не прыгал - fadeIn( - animationSpec = tween( - durationMillis = 200, - easing = FastOutSlowInEasing - ) - ) togetherWith fadeOut( - animationSpec = tween( - durationMillis = 150, - easing = FastOutSlowInEasing - ) - ) using SizeTransform(clip = false) + val isEnteringChat = targetState.first != null && initialState.first == null + val isExitingChat = targetState.first == null && initialState.first != null + val isEnteringSearch = targetState.second && !initialState.second + val isExitingSearch = !targetState.second && initialState.second + + when { + // 🚀 Вход в чат - slide справа + fade (как Telegram) + isEnteringChat -> { + slideInHorizontally( + initialOffsetX = { fullWidth -> fullWidth / 3 }, // Начинаем на 1/3 экрана справа + animationSpec = tween( + durationMillis = 250, + easing = FastOutSlowInEasing + ) + ) + fadeIn( + initialAlpha = 0.3f, + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing + ) + ) togetherWith slideOutHorizontally( + targetOffsetX = { fullWidth -> -fullWidth / 6 }, // Список уходит немного влево + animationSpec = tween( + durationMillis = 250, + easing = FastOutSlowInEasing + ) + ) + fadeOut( + targetAlpha = 0.5f, + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing + ) + ) + } + + // 🔙 Выход из чата - slide вправо + fade (быстрее) + isExitingChat -> { + slideInHorizontally( + initialOffsetX = { fullWidth -> -fullWidth / 6 }, // Список возвращается слева + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing + ) + ) + fadeIn( + initialAlpha = 0.5f, + animationSpec = tween( + durationMillis = 180, + easing = FastOutSlowInEasing + ) + ) togetherWith slideOutHorizontally( + targetOffsetX = { fullWidth -> fullWidth / 2 }, // Чат уходит вправо + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing + ) + ) + fadeOut( + animationSpec = tween( + durationMillis = 180, + easing = FastOutSlowInEasing + ) + ) + } + + // 🔍 Вход в поиск - slide снизу + isEnteringSearch -> { + slideInVertically( + initialOffsetY = { fullHeight -> fullHeight / 4 }, + animationSpec = tween( + durationMillis = 220, + easing = FastOutSlowInEasing + ) + ) + fadeIn( + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing + ) + ) togetherWith fadeOut( + animationSpec = tween( + durationMillis = 150, + easing = FastOutSlowInEasing + ) + ) + } + + // ❌ Выход из поиска - slide вниз + isExitingSearch -> { + fadeIn( + animationSpec = tween( + durationMillis = 180, + easing = FastOutSlowInEasing + ) + ) togetherWith slideOutVertically( + targetOffsetY = { fullHeight -> fullHeight / 4 }, + animationSpec = tween( + durationMillis = 200, + easing = FastOutSlowInEasing + ) + ) + fadeOut( + animationSpec = tween( + durationMillis = 150, + easing = FastOutSlowInEasing + ) + ) + } + + // Default fade + else -> { + fadeIn( + animationSpec = tween(durationMillis = 200) + ) togetherWith fadeOut( + animationSpec = tween(durationMillis = 150) + ) + } + } }, label = "screenNavigation" ) { (user, isSearchOpen, _) -> diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index bb3f399..90db50d 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.Immutable +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -194,9 +195,9 @@ fun ChatsListScreen( // Status dialog state var showStatusDialog by remember { mutableStateOf(false) } - var visible by remember { mutableStateOf(false) } - - LaunchedEffect(Unit) { visible = true } + // 🔥 Используем rememberSaveable чтобы сохранить состояние при навигации + // Header сразу visible = true, без анимации при возврате из чата + var visible by rememberSaveable { mutableStateOf(true) } // Dev console dialog - commented out for now /*