feat: Replace alpha animations with crossfade for smoother transitions in ChatsListScreen
This commit is contained in:
@@ -416,14 +416,41 @@ fun ChatsListScreen(
|
||||
)
|
||||
) {
|
||||
key(isDarkTheme) {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
// Burger menu - скрывается при поиске
|
||||
if (!isSearchExpanded) {
|
||||
// Custom header с фиксированной структурой для плавной анимации без скачков
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding(),
|
||||
color = backgroundColor
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
) {
|
||||
// Burger menu - всегда на месте, только меняет alpha
|
||||
val burgerAlpha by animateFloatAsState(
|
||||
targetValue = if (isSearchExpanded) 0f else 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 250,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
label = "burgerAlpha"
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (!isSearchExpanded) {
|
||||
scope.launch { drawerState.open() }
|
||||
}
|
||||
},
|
||||
enabled = !isSearchExpanded,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.padding(start = 4.dp)
|
||||
.graphicsLayer {
|
||||
alpha = burgerAlpha
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Menu,
|
||||
@@ -431,9 +458,14 @@ fun ChatsListScreen(
|
||||
tint = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
|
||||
// Center content - Title и Search в одном месте
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 60.dp)
|
||||
) {
|
||||
val focusRequester = remember {
|
||||
FocusRequester()
|
||||
} // Auto-focus when search opens
|
||||
@@ -444,82 +476,21 @@ fun ChatsListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Animate alpha for smooth fade without position changes
|
||||
val titleAlpha by animateFloatAsState(
|
||||
targetValue = if (isSearchExpanded) 0f else 1f,
|
||||
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
|
||||
label = "titleAlpha"
|
||||
)
|
||||
val searchAlpha by animateFloatAsState(
|
||||
targetValue = if (isSearchExpanded) 1f else 0f,
|
||||
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing),
|
||||
label = "searchAlpha"
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
// Title - Triple click to open dev console (stays in place, just fades)
|
||||
if (titleAlpha > 0.01f) {
|
||||
// Crossfade for smooth transition without position jumps
|
||||
Crossfade(
|
||||
targetState = isSearchExpanded,
|
||||
animationSpec = tween(
|
||||
durationMillis = 300,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "searchCrossfade"
|
||||
) { expanded ->
|
||||
if (expanded) {
|
||||
// Search Mode
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
val currentTime =
|
||||
System.currentTimeMillis()
|
||||
if (currentTime -
|
||||
lastClickTime <
|
||||
500
|
||||
) {
|
||||
titleClickCount++
|
||||
if (titleClickCount >=
|
||||
3
|
||||
) {
|
||||
showDevConsole =
|
||||
true
|
||||
titleClickCount =
|
||||
0
|
||||
}
|
||||
} else {
|
||||
titleClickCount =
|
||||
1
|
||||
}
|
||||
lastClickTime =
|
||||
currentTime
|
||||
}
|
||||
.graphicsLayer { alpha = titleAlpha },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"Rosetta",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
// Status indicator dot
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
when (protocolState) {
|
||||
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50) // Green
|
||||
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107) // Yellow
|
||||
ProtocolState.DISCONNECTED -> Color(0xFFF44336) // Red
|
||||
}
|
||||
)
|
||||
.clickable {
|
||||
showStatusDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Search TextField (appears on top with fade)
|
||||
if (searchAlpha > 0.01f) {
|
||||
Row(
|
||||
verticalAlignment =
|
||||
Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.graphicsLayer { alpha = searchAlpha }
|
||||
) {
|
||||
// Back arrow
|
||||
IconButton(
|
||||
@@ -638,61 +609,96 @@ fun ChatsListScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Title Mode - Triple click to open dev console
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val currentTime =
|
||||
System.currentTimeMillis()
|
||||
if (currentTime -
|
||||
lastClickTime <
|
||||
500
|
||||
) {
|
||||
titleClickCount++
|
||||
if (titleClickCount >=
|
||||
3
|
||||
) {
|
||||
showDevConsole =
|
||||
true
|
||||
titleClickCount =
|
||||
0
|
||||
}
|
||||
} else {
|
||||
titleClickCount =
|
||||
1
|
||||
}
|
||||
lastClickTime =
|
||||
currentTime
|
||||
},
|
||||
actions = {
|
||||
// Animated search button with fade
|
||||
val searchButtonScale by
|
||||
animateFloatAsState(
|
||||
targetValue =
|
||||
if (isSearchExpanded) 0f
|
||||
else 1f,
|
||||
animationSpec =
|
||||
tween(
|
||||
durationMillis = 200,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"Rosetta",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
// Status indicator dot
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
when (protocolState) {
|
||||
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50) // Green
|
||||
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107) // Yellow
|
||||
ProtocolState.DISCONNECTED -> Color(0xFFF44336) // Red
|
||||
}
|
||||
)
|
||||
.clickable {
|
||||
showStatusDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search button - справа, всегда на месте
|
||||
val searchButtonScale by animateFloatAsState(
|
||||
targetValue = if (isSearchExpanded) 0f else 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 250,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
label = "searchButtonScale"
|
||||
)
|
||||
val searchButtonAlpha by
|
||||
animateFloatAsState(
|
||||
targetValue =
|
||||
if (isSearchExpanded) 0f
|
||||
else 1f,
|
||||
animationSpec =
|
||||
tween(
|
||||
durationMillis = 200,
|
||||
val searchButtonAlpha by animateFloatAsState(
|
||||
targetValue = if (isSearchExpanded) 0f else 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 250,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
label = "searchButtonAlpha"
|
||||
)
|
||||
|
||||
if (searchButtonScale > 0.01f) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (protocolState ==
|
||||
ProtocolState
|
||||
.AUTHENTICATED
|
||||
) {
|
||||
if (protocolState == ProtocolState.AUTHENTICATED) {
|
||||
searchViewModel.expandSearch()
|
||||
}
|
||||
},
|
||||
enabled =
|
||||
protocolState ==
|
||||
ProtocolState.AUTHENTICATED,
|
||||
modifier =
|
||||
Modifier.graphicsLayer {
|
||||
enabled = protocolState == ProtocolState.AUTHENTICATED && !isSearchExpanded,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(end = 4.dp)
|
||||
.graphicsLayer {
|
||||
scaleX = searchButtonScale
|
||||
scaleY = searchButtonScale
|
||||
alpha =
|
||||
searchButtonAlpha *
|
||||
if (protocolState ==
|
||||
ProtocolState
|
||||
.AUTHENTICATED
|
||||
)
|
||||
1f
|
||||
else 0.5f
|
||||
alpha = searchButtonAlpha * if (protocolState == ProtocolState.AUTHENTICATED) 1f else 0.5f
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
@@ -702,16 +708,7 @@ fun ChatsListScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors =
|
||||
TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = backgroundColor,
|
||||
scrolledContainerColor = backgroundColor,
|
||||
navigationIconContentColor = textColor,
|
||||
titleContentColor = textColor,
|
||||
actionIconContentColor = textColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user