diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt index 700e68c..3f2e13e 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt @@ -312,50 +312,94 @@ fun ProfileScreen( // Отслеживаем активный drag var isDragging by remember { mutableStateOf(false) } + // Отслеживаем направление последнего скролла: true = вверх (к кругу), false = вниз (к квадрату) + var lastScrollDirectionUp by remember { mutableStateOf(false) } + // Calculate collapse progress (0 = expanded, 1 = collapsed, negative = overscroll) val collapseProgress by remember { derivedStateOf { (animatedScrollOffset.value / maxScrollOffset).coerceIn(-1f, 1f) } } + // Определяем целевое состояние: true = круг (scrollOffset >= 0), false = квадрат (scrollOffset + // < snapThreshold) + val targetIsCircle by remember { + derivedStateOf { + // Решение: круг если scrollOffset >= snapThreshold (50% от минимального) + scrollOffset >= snapThreshold + } + } + + // Текущее отображаемое состояние (для предотвращения мерцания) + var displayedIsCircle by remember { mutableStateOf(true) } + // Overscroll progress - просто на основе scrollOffset val overscrollProgress by remember { derivedStateOf { - val progress = + // Если мы в режиме "круг", всегда показываем 0 (полный круг) + // Если в режиме "квадрат", показываем 1 (полный квадрат) + val rawProgress = if (animatedScrollOffset.value < 0f) { (-animatedScrollOffset.value / (-minScrollOffset)).coerceIn(0f, 1f) } else { 0f } + + // Дискретный прогресс на основе displayedIsCircle + val discreteProgress = if (displayedIsCircle) 0f else 1f + Log.d( TAG, - "📊 overscrollProgress=$progress, scrollOffset=${animatedScrollOffset.value}, minScrollOffset=$minScrollOffset" + "📊 overscrollProgress: raw=$rawProgress, discrete=$discreteProgress, displayedIsCircle=$displayedIsCircle, targetIsCircle=$targetIsCircle, scrollOffset=${animatedScrollOffset.value}" ) - progress + discreteProgress } } - // Snap анимация при отпускании - LaunchedEffect(isDragging) { - if (!isDragging && scrollOffset < 0f) { - // Отпустили палец в overscroll зоне - snap к ближайшему состоянию - val targetOffset = - if (scrollOffset < snapThreshold) { - minScrollOffset // Snap к квадрату - } else { - 0f // Snap к кругу - } - Log.d(TAG, "🎯 Snap animation: from $scrollOffset to $targetOffset") - animatedScrollOffset.animateTo( - targetValue = targetOffset, - animationSpec = - spring( - dampingRatio = - androidx.compose.animation.core.Spring - .DampingRatioMediumBouncy, - stiffness = androidx.compose.animation.core.Spring.StiffnessLow - ) + // Обновляем displayedIsCircle только при отпускании пальца (snap) + LaunchedEffect(isDragging, targetIsCircle) { + Log.d( + TAG, + "🎯 LaunchedEffect: isDragging=$isDragging, targetIsCircle=$targetIsCircle, displayedIsCircle=$displayedIsCircle, scrollOffset=$scrollOffset, lastScrollDirectionUp=$lastScrollDirectionUp" + ) + + if (!isDragging) { + // Палец отпущен - snap на основе направления скролла + // Если скроллили ВВЕРХ - snap к кругу, если ВНИЗ - snap к квадрату + val newIsCircle = lastScrollDirectionUp || scrollOffset >= 0f + Log.d( + TAG, + "👆 Finger released: scrollOffset=$scrollOffset, lastScrollDirectionUp=$lastScrollDirectionUp, newIsCircle=$newIsCircle" ) - scrollOffset = targetOffset + + if (scrollOffset < 0f) { + // Были в overscroll зоне + val targetOffset = if (newIsCircle) 0f else minScrollOffset + Log.d( + TAG, + "🎯 Snap animation: from $scrollOffset to $targetOffset (circle=$newIsCircle)" + ) + + // Сначала обновляем состояние формы + displayedIsCircle = newIsCircle + + // Потом анимируем позицию + animatedScrollOffset.animateTo( + targetValue = targetOffset, + animationSpec = + spring( + dampingRatio = + androidx.compose.animation.core.Spring + .DampingRatioMediumBouncy, + stiffness = + androidx.compose.animation.core.Spring.StiffnessLow + ) + ) + scrollOffset = targetOffset + } else if (scrollOffset >= 0f) { + // Обычный скролл - всегда круг + displayedIsCircle = true + animatedScrollOffset.snapTo(scrollOffset) + } } } @@ -363,6 +407,7 @@ fun ProfileScreen( LaunchedEffect(scrollOffset, isDragging) { if (isDragging) { animatedScrollOffset.snapTo(scrollOffset) + Log.d(TAG, "🔄 Sync during drag: scrollOffset=$scrollOffset -> animatedScrollOffset") } } @@ -373,17 +418,40 @@ fun ProfileScreen( val delta = available.y val newOffset = scrollOffset - delta + // Отслеживаем, что палец активен + val wasDragging = isDragging isDragging = true + // Отслеживаем направление скролла (только значимые движения) + if (kotlin.math.abs(delta) > 3f) { + // delta < 0 = палец идёт вверх (скролл контента вниз, возврат к кругу) + // delta > 0 = палец идёт вниз (скролл контента вверх, к квадрату) + lastScrollDirectionUp = delta < 0f + Log.d( + TAG, + "🧭 Direction changed: delta=$delta, lastScrollDirectionUp=$lastScrollDirectionUp" + ) + } + Log.d( TAG, - "🔄 onPreScroll: delta=$delta, scrollOffset=$scrollOffset, newOffset=$newOffset" + "🔄 onPreScroll: delta=$delta, scrollOffset=$scrollOffset, newOffset=$newOffset, wasDragging=$wasDragging, displayedIsCircle=$displayedIsCircle, lastScrollDirectionUp=$lastScrollDirectionUp" ) - // Если скроллим вверх (delta < 0) и были в overscroll - плавно возвращаемся - if (delta < -5f && scrollOffset < 0f) { - Log.d(TAG, "⚡ Scroll up from overscroll: scrollOffset=$scrollOffset") - scrollOffset = (scrollOffset - delta * 0.5f).coerceIn(minScrollOffset, 0f) + // Если скроллим вверх (delta < 0) и были в overscroll зоне - возвращаемся к кругу + if (delta < 0f && scrollOffset < 0f) { + // Плавно уменьшаем overscroll + val newValue = (scrollOffset - delta).coerceAtMost(0f) + scrollOffset = newValue + + // Когда достигли 0, переключаемся на круг + if (scrollOffset >= 0f) { + Log.d(TAG, "⚡ Reached 0 from overscroll - switching to circle") + displayedIsCircle = true + scrollOffset = 0f + } + + Log.d(TAG, "📤 Scroll up in overscroll: scrollOffset=$scrollOffset") return Offset(0f, -delta) } @@ -413,8 +481,12 @@ fun ProfileScreen( } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + Log.d( + TAG, + "👆 onPostFling: scrollOffset=$scrollOffset, displayedIsCircle=$displayedIsCircle, lastScrollDirectionUp=$lastScrollDirectionUp" + ) isDragging = false - Log.d(TAG, "👆 onPostFling: drag ended, isDragging=false") + Log.d(TAG, "👆 onPostFling: drag ended, isDragging=false, triggering snap") return super.onPostFling(consumed, available) } }