From d182414cdcd73d76e5ca986983b257f0e458a708 Mon Sep 17 00:00:00 2001 From: BoD Date: Sun, 31 Mar 2024 17:22:43 +0200 Subject: [PATCH] Add a red blinking dot for ongoing sessions and scroll to next session --- .../wear/ui/common/PulsatingRedDot.kt | 47 +++++++++++++++++++ .../wear/ui/session/UISession.kt | 23 +++++++-- .../ui/session/details/SessionDetailScreen.kt | 5 ++ .../wear/ui/session/list/SessionListScreen.kt | 38 ++++++++++++++- wearApp/src/main/res/values/styles.xml | 1 + 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/PulsatingRedDot.kt diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/PulsatingRedDot.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/PulsatingRedDot.kt new file mode 100644 index 00000000..2911f8bf --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/PulsatingRedDot.kt @@ -0,0 +1,47 @@ +package fr.paug.androidmakers.wear.ui.common + +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.unit.dp +import fr.paug.androidmakers.wear.ui.theme.amRed + +@Composable +fun Pulsating(content: @Composable () -> Unit) { + val infiniteTransition = rememberInfiniteTransition(label = "pulse") + + val scale by infiniteTransition.animateFloat( + label = "pulse", + initialValue = 1f, + targetValue = 0f, + animationSpec = infiniteRepeatable( + animation = tween(500), + repeatMode = RepeatMode.Reverse + ) + ) + + Box(modifier = Modifier.alpha(scale)) { + content() + } +} + +@Composable +fun PulsatingRedDot() { + Pulsating { + Box( + modifier = Modifier + .size(8.dp) + .background(color = amRed, shape = CircleShape) + ) + } +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt index 636a1743..aef4d80f 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt @@ -3,12 +3,27 @@ package fr.paug.androidmakers.wear.ui.session import fr.androidmakers.domain.model.Room import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.model.Speaker +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime data class UISession( - val session: Session, - val speakers: List, - val room: Room, - val isBookmarked: Boolean, + val session: Session, + val speakers: List, + val room: Room, + val isBookmarked: Boolean, ) { val formattedDuration: String = session.duration.inWholeMinutes.toString() + " min" + + val isOver: Boolean + get() { + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + return session.endsAt < now + } + + val isOngoing: Boolean + get() { + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + return session.startsAt <= now && now <= session.endsAt + } } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt index 8fc2a790..e88df54e 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt @@ -34,6 +34,7 @@ import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults import com.google.android.horologist.compose.layout.ScreenScaffold import com.google.android.horologist.compose.layout.rememberResponsiveColumnState import fr.paug.androidmakers.wear.ui.common.Loading +import fr.paug.androidmakers.wear.ui.common.PulsatingRedDot import fr.paug.androidmakers.wear.ui.session.UISession import fr.paug.androidmakers.wear.ui.session.uiSession1 import fr.paug.androidmakers.wear.ui.theme.amRed @@ -86,6 +87,10 @@ private fun Session(session: UISession) { Spacer(modifier = Modifier.width(16.dp)) + if (session.isOngoing) { + PulsatingRedDot() + Spacer(modifier = Modifier.width(2.dp)) + } Text( text = session.formattedDuration, ) diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt index 1b1e85b0..39b53650 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt @@ -14,8 +14,11 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.style.LineBreak @@ -39,6 +42,7 @@ import com.google.android.horologist.compose.material.ListHeaderDefaults import com.google.android.horologist.compose.material.ResponsiveListHeader import fr.paug.androidmakers.wear.R import fr.paug.androidmakers.wear.ui.common.Loading +import fr.paug.androidmakers.wear.ui.common.PulsatingRedDot import fr.paug.androidmakers.wear.ui.session.UISession import fr.paug.androidmakers.wear.ui.session.uiSessions import fr.paug.androidmakers.wear.ui.theme.amRed @@ -72,6 +76,22 @@ private fun SessionList( last = ScalingLazyColumnDefaults.ItemType.Card, ) ) + + // Approximation of about half the height of a card, so the card is centered when scrolling to it. + // Maybe there's a way to get the actual height? + val scrollOffset = with(LocalDensity.current) { 80.dp.roundToPx() } + + val nextSessionIndex = sessions.nextSessionIndex() + if (nextSessionIndex > 0) { + LaunchedEffect(Unit) { + columnState.state.scrollToItem( + // Add 1 to the index to account for the title + index = nextSessionIndex + 1, + scrollOffset = scrollOffset, + ) + } + } + ScreenScaffold(scrollState = columnState) { ScalingLazyColumn( columnState = columnState, @@ -103,13 +123,25 @@ private fun SessionList( } } +private fun List.nextSessionIndex(): Int { + return indexOfFirst { !it.isOver } +} + @Composable private fun SessionItem( session: UISession, onSessionClick: (String) -> Unit, ) { TitleCard( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .let { + if (session.isOver) { + it.alpha(.7F) + } else { + it + } + }, title = { Column( modifier = Modifier.fillMaxWidth(), @@ -138,6 +170,10 @@ private fun SessionItem( text = session.session.startsAt.time.toString(), ) + if (session.isOngoing) { + PulsatingRedDot() + Spacer(modifier = Modifier.width(2.dp)) + } Text( text = session.formattedDuration, ) diff --git a/wearApp/src/main/res/values/styles.xml b/wearApp/src/main/res/values/styles.xml index c873d0d8..d4d34527 100644 --- a/wearApp/src/main/res/values/styles.xml +++ b/wearApp/src/main/res/values/styles.xml @@ -5,5 +5,6 @@ @drawable/splash_icon @android:style/Theme.DeviceDefault icon_preferred + @android:color/transparent