From 3edc8632be157491e17cc235a15a0e372c15f88b Mon Sep 17 00:00:00 2001 From: Nedifinita Date: Tue, 22 Jul 2025 23:18:50 +0800 Subject: [PATCH] feat: add animation effects and visual feedback --- .../org/noxylva/lbjconsole/MainActivity.kt | 175 ++++++++++-- .../ui/components/TrainRecordsList.kt | 52 +++- .../lbjconsole/ui/screens/HistoryScreen.kt | 270 +++++++++++------- .../lbjconsole/ui/screens/MonitorScreen.kt | 71 ++++- 4 files changed, 428 insertions(+), 140 deletions(-) diff --git a/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt b/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt index cf95af0..220b126 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt @@ -20,20 +20,31 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.ui.graphics.toArgb import androidx.core.view.WindowCompat +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.LocationOn +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import kotlinx.coroutines.delay @@ -966,55 +977,165 @@ fun ConnectionDialog( ) { AlertDialog( onDismissRequest = onDismiss, - title = { Text("连接设备") }, + title = { + Text( + text = "蓝牙设备", + style = MaterialTheme.typography.headlineSmall + ) + }, text = { - Column(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier.fillMaxWidth() + ) { Button( onClick = onScan, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = if (isScanning) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary + ) ) { - Text(if (isScanning) "停止扫描" else "扫描设备") + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (isScanning) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp, + color = MaterialTheme.colorScheme.onPrimary + ) + } else { + Icon( + imageVector = Icons.Default.Search, + contentDescription = null + ) + } + Text( + text = if (isScanning) "扫描中..." else "扫描设备", + fontWeight = FontWeight.Medium + ) + } } - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(16.dp)) - if (isScanning) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - Spacer(modifier = Modifier.height(8.dp)) - } - - if (devices.isEmpty()) { - Text("未找到设备") - } else { - Column { - devices.forEach { device -> + if (devices.isNotEmpty()) { + Text( + text = "发现 ${devices.size} 个设备", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + + LazyColumn( + modifier = Modifier.heightIn(max = 200.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(devices) { device -> + var isPressed by remember { mutableStateOf(false) } + + val cardScale by animateFloatAsState( + targetValue = if (isPressed) 0.98f else 1f, + animationSpec = tween( + durationMillis = 120, + easing = LinearEasing + ) + ) + + LaunchedEffect(isPressed) { + if (isPressed) { + delay(100) + isPressed = false + } + } + Card( modifier = Modifier .fillMaxWidth() - .padding(vertical = 4.dp) - .clickable { onConnect(device) } + .graphicsLayer { + scaleX = cardScale + scaleY = cardScale + }, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { - Column( - modifier = Modifier.padding(8.dp) + Row( + modifier = Modifier + .fillMaxWidth() + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = true) + ) { + isPressed = true + onConnect(device) + } + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - Text( - text = device.name ?: "未知设备", - fontWeight = FontWeight.Bold - ) - Text( - text = device.address, - style = MaterialTheme.typography.bodySmall + Icon( + imageVector = Icons.Default.Bluetooth, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) ) + + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = device.name ?: "未知设备", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = device.address, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } } } + } else if (!isScanning) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = Icons.Default.BluetoothSearching, + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "未发现设备", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "请确保设备已开启并处于可发现状态", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), + textAlign = TextAlign.Center + ) + } + } } } }, confirmButton = { TextButton(onClick = onDismiss) { - Text("取消") + Text("关闭") } } ) diff --git a/app/src/main/java/org/noxylva/lbjconsole/ui/components/TrainRecordsList.kt b/app/src/main/java/org/noxylva/lbjconsole/ui/components/TrainRecordsList.kt index 88488d5..a144c47 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/ui/components/TrainRecordsList.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/ui/components/TrainRecordsList.kt @@ -1,18 +1,26 @@ package org.noxylva.lbjconsole.ui.components +import androidx.compose.animation.* +import androidx.compose.animation.core.* +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.FilterList import androidx.compose.material.icons.filled.Share +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -21,6 +29,7 @@ import org.noxylva.lbjconsole.model.TrainRecord import java.text.SimpleDateFormat import java.util.* +@OptIn(ExperimentalFoundationApi::class) @Composable fun TrainRecordsList( records: List, @@ -41,19 +50,52 @@ fun TrainRecordsList( } else { LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(vertical = 4.dp, horizontal = 8.dp) + contentPadding = PaddingValues(vertical = 4.dp, horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - items(records) { record -> + itemsIndexed(records, key = { _, record -> record.uniqueId }) { index, record -> + val animationDelay = (index * 30).coerceAtMost(200) + var isPressed by remember { mutableStateOf(false) } + + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.98f else 1f, + animationSpec = tween(durationMillis = 120) + ) + + val elevation by animateDpAsState( + targetValue = if (isPressed) 6.dp else 2.dp, + animationSpec = tween(durationMillis = 120) + ) + + LaunchedEffect(isPressed) { + if (isPressed) { + kotlinx.coroutines.delay(150) + isPressed = false + } + } + Card( modifier = Modifier .fillMaxWidth() - .padding(vertical = 2.dp) - .clickable { onRecordClick(record) }, - elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .animateItemPlacement( + animationSpec = tween(durationMillis = 200) + ), + elevation = CardDefaults.cardElevation(defaultElevation = elevation) ) { Row( modifier = Modifier .fillMaxWidth() + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = true) + ) { + isPressed = true + onRecordClick(record) + } .padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically diff --git a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt index e99be82..e2b63bd 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt @@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items +import androidx.compose.animation.* +import androidx.compose.animation.core.* +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ripple.rememberRipple @@ -19,6 +22,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -53,14 +57,41 @@ fun TrainRecordItem( onToggleSelection: (TrainRecord) -> Unit, onLongClick: (TrainRecord) -> Unit ) { - val cardColor = when { - isSelected -> MaterialTheme.colorScheme.primaryContainer - else -> MaterialTheme.colorScheme.surface - } + val recordId = record.uniqueId + val isExpanded = expandedStatesMap[recordId] == true + + val cardColor by animateColorAsState( + targetValue = when { + isSelected -> MaterialTheme.colorScheme.primaryContainer + else -> MaterialTheme.colorScheme.surface + }, + animationSpec = tween(durationMillis = 200), + label = "cardColor" + ) + + val cardScale by animateFloatAsState( + targetValue = if (isSelected) 0.98f else 1f, + animationSpec = tween(durationMillis = 150), + label = "cardScale" + ) + + val cardElevation by animateDpAsState( + targetValue = if (isSelected) 6.dp else 2.dp, + animationSpec = tween(durationMillis = 200), + label = "cardElevation" + ) Card( - modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + modifier = Modifier + .fillMaxWidth() + .graphicsLayer { + scaleX = cardScale + scaleY = cardScale + } + .animateContentSize( + animationSpec = tween(durationMillis = 150, easing = LinearEasing) + ), + elevation = CardDefaults.cardElevation(defaultElevation = cardElevation), colors = CardDefaults.cardColors( containerColor = cardColor ), @@ -260,106 +291,112 @@ fun TrainRecordItem( } } - if (isExpanded) { - val coordinates = remember { record.getCoordinates() } + AnimatedVisibility( + visible = isExpanded, + enter = expandVertically(animationSpec = tween(durationMillis = 300)) + fadeIn(animationSpec = tween(durationMillis = 300)), + exit = shrinkVertically(animationSpec = tween(durationMillis = 300)) + fadeOut(animationSpec = tween(durationMillis = 300)) + ) { + Column { + val coordinates = remember { record.getCoordinates() } - if (coordinates != null) { - Spacer(modifier = Modifier.height(8.dp)) - } + if (coordinates != null) { + Spacer(modifier = Modifier.height(8.dp)) + } - if (coordinates != null) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(220.dp) - .padding(vertical = 4.dp) - .clip(RoundedCornerShape(8.dp)), - contentAlignment = Alignment.Center - ) { - AndroidView( - modifier = Modifier.clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) {}, - factory = { context -> - MapView(context).apply { - setTileSource(TileSourceFactory.MAPNIK) - setMultiTouchControls(true) - zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER) - isHorizontalMapRepetitionEnabled = false - isVerticalMapRepetitionEnabled = false - setHasTransientState(true) - setOnTouchListener { v, event -> - v.parent?.requestDisallowInterceptTouchEvent(true) - false - } - controller.setZoom(10.0) - controller.setCenter(coordinates) - this.isTilesScaledToDpi = true - this.setUseDataConnection(true) + if (coordinates != null) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(220.dp) + .padding(vertical = 4.dp) + .clip(RoundedCornerShape(8.dp)), + contentAlignment = Alignment.Center + ) { + AndroidView( + modifier = Modifier.clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) {}, + factory = { context -> + MapView(context).apply { + setTileSource(TileSourceFactory.MAPNIK) + setMultiTouchControls(true) + zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER) + isHorizontalMapRepetitionEnabled = false + isVerticalMapRepetitionEnabled = false + setHasTransientState(true) + setOnTouchListener { v, event -> + v.parent?.requestDisallowInterceptTouchEvent(true) + false + } + controller.setZoom(10.0) + controller.setCenter(coordinates) + this.isTilesScaledToDpi = true + this.setUseDataConnection(true) - try { - val railwayTileSource = XYTileSource( - "OpenRailwayMap", 8, 16, 256, ".png", - arrayOf( - "https://a.tiles.openrailwaymap.org/standard/", - "https://b.tiles.openrailwaymap.org/standard/", - "https://c.tiles.openrailwaymap.org/standard/" - ), - "© OpenRailwayMap contributors, © OpenStreetMap contributors" - ) + try { + val railwayTileSource = XYTileSource( + "OpenRailwayMap", 8, 16, 256, ".png", + arrayOf( + "https://a.tiles.openrailwaymap.org/standard/", + "https://b.tiles.openrailwaymap.org/standard/", + "https://c.tiles.openrailwaymap.org/standard/" + ), + "© OpenRailwayMap contributors, © OpenStreetMap contributors" + ) - val railwayProvider = MapTileProviderBasic(context) - railwayProvider.tileSource = railwayTileSource + val railwayProvider = MapTileProviderBasic(context) + railwayProvider.tileSource = railwayTileSource - val railwayOverlay = TilesOverlay(railwayProvider, context) - railwayOverlay.loadingBackgroundColor = android.graphics.Color.TRANSPARENT - railwayOverlay.loadingLineColor = android.graphics.Color.TRANSPARENT + val railwayOverlay = TilesOverlay(railwayProvider, context) + railwayOverlay.loadingBackgroundColor = android.graphics.Color.TRANSPARENT + railwayOverlay.loadingLineColor = android.graphics.Color.TRANSPARENT - overlays.add(railwayOverlay) - } catch (e: Exception) { - e.printStackTrace() - } - - try { - val locationProvider = GpsMyLocationProvider(context).apply { - locationUpdateMinDistance = 10f - locationUpdateMinTime = 1000 + overlays.add(railwayOverlay) + } catch (e: Exception) { + e.printStackTrace() } - MyLocationNewOverlay(locationProvider, this).apply { - enableMyLocation() - }.also { overlays.add(it) } - } catch (e: Exception) { - e.printStackTrace() + try { + val locationProvider = GpsMyLocationProvider(context).apply { + locationUpdateMinDistance = 10f + locationUpdateMinTime = 1000 + } + + MyLocationNewOverlay(locationProvider, this).apply { + enableMyLocation() + }.also { overlays.add(it) } + } catch (e: Exception) { + e.printStackTrace() + } + + val marker = Marker(this) + marker.position = coordinates + + val latStr = String.format("%.4f", coordinates.latitude) + val lonStr = String.format("%.4f", coordinates.longitude) + val coordStr = "${latStr}°N, ${lonStr}°E" + marker.title = recordMap["train"]?.toString() ?: "列车" + marker.snippet = coordStr + marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, 0f) + + overlays.add(marker) + marker.showInfoWindow() } - - val marker = Marker(this) - marker.position = coordinates - - val latStr = String.format("%.4f", coordinates.latitude) - val lonStr = String.format("%.4f", coordinates.longitude) - val coordStr = "${latStr}°N, ${lonStr}°E" - marker.title = recordMap["train"]?.toString() ?: "列车" - marker.snippet = coordStr - marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, 0f) - - overlays.add(marker) - marker.showInfoWindow() - } - }, - update = { mapView -> mapView.invalidate() } + }, + update = { mapView -> mapView.invalidate() } + ) + } + } + if (recordMap.containsKey("position_info")) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = recordMap["position_info"] ?: "", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface ) } } - if (recordMap.containsKey("position_info")) { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = recordMap["position_info"] ?: "", - fontSize = 14.sp, - color = MaterialTheme.colorScheme.onSurface - ) - } } } } @@ -383,14 +420,39 @@ fun MergedTrainRecordItem( val latestRecord = mergedRecord.latestRecord val hasSelectedRecords = mergedRecord.records.any { selectedRecords.contains(it) } - val cardColor = when { - hasSelectedRecords -> MaterialTheme.colorScheme.primaryContainer - else -> MaterialTheme.colorScheme.surface - } + + val cardColor by animateColorAsState( + targetValue = when { + hasSelectedRecords -> MaterialTheme.colorScheme.primaryContainer + else -> MaterialTheme.colorScheme.surface + }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing), + label = "mergedCardColor" + ) + + val cardScale by animateFloatAsState( + targetValue = if (hasSelectedRecords) 0.98f else 1f, + animationSpec = tween(durationMillis = 150, easing = LinearEasing), + label = "mergedCardScale" + ) + + val cardElevation by animateDpAsState( + targetValue = if (hasSelectedRecords) 6.dp else 2.dp, + animationSpec = tween(durationMillis = 200, easing = LinearEasing), + label = "mergedCardElevation" + ) Card( - modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + modifier = Modifier + .fillMaxWidth() + .graphicsLayer { + scaleX = cardScale + scaleY = cardScale + } + .animateContentSize( + animationSpec = tween(durationMillis = 150, easing = LinearEasing) + ), + elevation = CardDefaults.cardElevation(defaultElevation = cardElevation), colors = CardDefaults.cardColors( containerColor = cardColor ), @@ -948,7 +1010,13 @@ fun HistoryScreen( verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp) ) { - items(filteredRecords) { item -> + itemsIndexed(filteredRecords, key = { _, item -> + when (item) { + is TrainRecord -> item.uniqueId + is MergedTrainRecord -> item.groupKey + else -> item.hashCode() + } + }) { index, item -> when (item) { is TrainRecord -> { TrainRecordItem( diff --git a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt index 427c8f2..c777eeb 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt @@ -1,13 +1,18 @@ package org.noxylva.lbjconsole.ui.screens +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.clickable + +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight - import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -28,6 +33,20 @@ fun MonitorScreen( ) { var showDetailDialog by remember { mutableStateOf(false) } var selectedRecord by remember { mutableStateOf(null) } + var isPressed by remember { mutableStateOf(false) } + + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.98f else 1f, + animationSpec = tween(durationMillis = 120), + label = "content_scale" + ) + + LaunchedEffect(isPressed) { + if (isPressed) { + delay(100) + isPressed = false + } + } val timeSinceLastUpdate = remember { mutableStateOf(null) } @@ -76,20 +95,57 @@ fun MonitorScreen( .fillMaxWidth() .weight(1f) ) { - if (latestRecord != null) { + AnimatedContent( + targetState = latestRecord, + transitionSpec = { + fadeIn( + animationSpec = tween( + durationMillis = 300, + easing = FastOutSlowInEasing + ) + ) + slideInVertically( + initialOffsetY = { it / 4 }, + animationSpec = tween( + durationMillis = 300, + easing = FastOutSlowInEasing + ) + ) togetherWith fadeOut( + animationSpec = tween( + durationMillis = 150, + easing = FastOutLinearInEasing + ) + ) + slideOutVertically( + targetOffsetY = { -it / 4 }, + animationSpec = tween( + durationMillis = 150, + easing = FastOutLinearInEasing + ) + ) + }, + label = "content_animation" + ) { record -> + if (record != null) { Column( modifier = Modifier .fillMaxWidth() - .padding(8.dp) - .clickable { - selectedRecord = latestRecord + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = true) + ) { + isPressed = true + selectedRecord = record showDetailDialog = true - onRecordClick(latestRecord) + onRecordClick(record) + } + .padding(8.dp) + .graphicsLayer { + scaleX = scale + scaleY = scale } ) { - val recordMap = latestRecord.toMap() + val recordMap = record.toMap() Row( @@ -209,6 +265,7 @@ fun MonitorScreen( } } } + } } }