diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt index e395df9..a6a6a1d 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt @@ -1,5 +1,6 @@ package com.derakkuma.ui.components +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -7,12 +8,15 @@ import androidx.compose.material3.Text 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.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.derakkuma.data.RecentPlay +import com.derakkuma.ui.images.MaimaiRemoteImage import com.derakkuma.ui.theme.* import com.fleeys.heatmap.HeatMap import com.fleeys.heatmap.model.Heat @@ -30,6 +34,7 @@ import kotlin.time.Clock fun PlayHeatmap( plays: List, modifier: Modifier, + onRecordClick: (() -> Unit)? = null, ) { val heats = remember(plays) { val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date @@ -63,11 +68,14 @@ fun PlayHeatmap( } var selectedDate by remember { mutableStateOf(null) } + var expandedDate by remember { mutableStateOf(null) } val inactiveHeatColor = MaterialTheme.colorScheme.surfaceVariant val activeHeatColor = MaterialTheme.colorScheme.secondary val selectedPlays = remember(plays, selectedDate) { selectedDate?.let { date -> plays.filter { parsePlayDate(it.datetime) == date } }.orEmpty() } + val isExpanded = selectedDate != null && expandedDate == selectedDate + val visiblePlays = if (isExpanded) selectedPlays else selectedPlays.take(6) ExpressiveGlassCard( modifier = modifier.fillMaxWidth(), @@ -98,6 +106,7 @@ fun PlayHeatmap( ), onHeatClick = { heat -> selectedDate = heat.date + expandedDate = null }, ) @@ -106,9 +115,11 @@ fun PlayHeatmap( val date = selectedDate!! val dateStr = "${date.month.name.take(3).lowercase().replaceFirstChar { it.uppercase() }} ${date.day}, ${date.year}" ExpressiveGlassCard( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .then(if (onRecordClick != null) Modifier.clickable { onRecordClick() } else Modifier), cornerRadius = 18.dp, - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.78f), + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.94f), ) { Column(Modifier.fillMaxWidth().padding(10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Text( @@ -120,16 +131,28 @@ fun PlayHeatmap( if (selectedPlays.isEmpty()) { Text("No records for this day", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } else { - selectedPlays.take(6).forEach { play -> - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + visiblePlays.forEach { play -> + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { Text(play.datetime.substringAfter(' ', ""), fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.width(36.dp)) + MaimaiRemoteImage( + url = play.albumArtUrl, + modifier = Modifier.size(22.dp).clip(RoundedCornerShape(5.dp)), + contentDescription = play.name, + contentScale = ContentScale.Crop, + ) Text(play.name, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurface, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f)) Text(play.difficulty.replaceFirstChar { it.uppercase() }, fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = difficultyColor(play.difficulty)) Text("${play.achievement}%", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.secondary) } } - if (selectedPlays.size > 6) { - Text("+${selectedPlays.size - 6} more", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + if (!isExpanded && selectedPlays.size > 6) { + Text( + "+${selectedPlays.size - 6} more", + modifier = Modifier.clickable { expandedDate = selectedDate }, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary, + ) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt index 2083035..951390b 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt @@ -38,7 +38,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @Composable -fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit)? = null, maintenanceMode: Boolean = false, cacheStore: CacheStore = CacheStore(), playStore: PlayStore? = null, atProtoClient: AtProtoClient? = null, onPlayClick: ((idx: String) -> Unit)? = null) { +fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit)? = null, maintenanceMode: Boolean = false, cacheStore: CacheStore = CacheStore(), playStore: PlayStore? = null, atProtoClient: AtProtoClient? = null, onPlayClick: ((idx: String) -> Unit)? = null, onRecordClick: (() -> Unit)? = null) { var data by remember { mutableStateOf(null) } var playerData by remember { mutableStateOf(null) } var loading by remember { mutableStateOf(true) } @@ -178,6 +178,7 @@ fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit) PlayHeatmap( plays = plays, modifier = Modifier.padding(horizontal = 12.dp), + onRecordClick = { onRecordClick?.invoke() }, ) val lastPlayed = data!!.playLog.firstOrNull() ?: plays.firstOrNull()