Skip to content
This repository was archived by the owner on Jul 20, 2026. It is now read-only.

Commit 25a6097

Browse files
Merge pull request #43 from TheRefraction/feature-animations
fancy animations implemented lads
2 parents bb975e3 + 527af4b commit 25a6097

3 files changed

Lines changed: 135 additions & 31 deletions

File tree

app/src/main/java/fr/utbm/sy43/pilulito/ui/screens/AccountInfoScreen.kt

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package fr.utbm.sy43.pilulito.ui.screens
22

33

4+
import androidx.compose.animation.core.LinearEasing
5+
import androidx.compose.animation.core.RepeatMode
6+
import androidx.compose.animation.core.animateFloat
7+
import androidx.compose.animation.core.infiniteRepeatable
8+
import androidx.compose.animation.core.rememberInfiniteTransition
9+
import androidx.compose.animation.core.tween
10+
import androidx.compose.foundation.background
411
import androidx.compose.foundation.layout.*
512
import androidx.compose.foundation.shape.RoundedCornerShape
613
import androidx.compose.material3.*
714
import androidx.compose.runtime.*
815
import androidx.compose.ui.Alignment
916
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.graphics.Brush
1018
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.graphics.lerp
1120
import androidx.compose.ui.text.font.FontWeight
1221
import androidx.compose.ui.unit.dp
1322
import androidx.compose.ui.unit.sp
@@ -63,11 +72,42 @@ fun AccountInfoScreen(
6372
}
6473
}
6574

75+
val infiniteTransition = rememberInfiniteTransition(label = "cardGradient")
76+
77+
val colorShift1 by infiniteTransition.animateFloat(
78+
initialValue = 0f,
79+
targetValue = 1f,
80+
animationSpec = infiniteRepeatable(
81+
animation = tween(durationMillis = 4000, easing = LinearEasing),
82+
repeatMode = RepeatMode.Reverse
83+
),
84+
label = "colorShift1"
85+
)
86+
val colorShift2 by infiniteTransition.animateFloat(
87+
initialValue = 1f,
88+
targetValue = 0f,
89+
animationSpec = infiniteRepeatable(
90+
animation = tween(durationMillis = 4000, easing = LinearEasing),
91+
repeatMode = RepeatMode.Reverse
92+
),
93+
label = "colorShift2"
94+
)
95+
6696
Card(
6797
shape = RoundedCornerShape(20.dp),
68-
colors = CardDefaults.cardColors(containerColor = OrangeLight),
98+
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
6999
elevation = CardDefaults.cardElevation(0.dp),
70-
modifier = Modifier.fillMaxWidth()
100+
modifier = Modifier
101+
.fillMaxWidth()
102+
.background(
103+
brush = Brush.linearGradient(
104+
colors = listOf(
105+
lerp(Color(0xFFFFE0B2), Color(0xFFFF8A65), colorShift1),
106+
lerp(Color(0xFFFFCC80), Color(0xFFE64A19), colorShift2)
107+
)
108+
),
109+
shape = RoundedCornerShape(20.dp)
110+
)
71111
) {
72112
Column(
73113
modifier = Modifier.padding(20.dp),
@@ -83,7 +123,7 @@ fun AccountInfoScreen(
83123
text = uiState.linkCode,
84124
fontSize = 32.sp,
85125
fontWeight = FontWeight.Bold,
86-
color = Orange
126+
color = Color(0xFFE53935)
87127
)
88128
Text(
89129
text = "Share this code with a family member so they can follow your medication.",
@@ -94,6 +134,7 @@ fun AccountInfoScreen(
94134
}
95135
}
96136

137+
97138
Spacer(modifier = Modifier.weight(1f))
98139

99140
Button(

app/src/main/java/fr/utbm/sy43/pilulito/ui/screens/HistoryScreen.kt

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package fr.utbm.sy43.pilulito.ui.screens
22

3+
import androidx.compose.animation.core.Animatable
4+
import androidx.compose.animation.core.FastOutSlowInEasing
5+
import androidx.compose.animation.core.tween
36
import androidx.compose.foundation.background
47
import androidx.compose.foundation.layout.*
58
import androidx.compose.foundation.lazy.LazyColumn
@@ -22,6 +25,7 @@ import fr.utbm.sy43.pilulito.ui.components.TopBarComponent
2225
import fr.utbm.sy43.pilulito.viewmodels.DayStats
2326
import fr.utbm.sy43.pilulito.viewmodels.DrugStat
2427
import fr.utbm.sy43.pilulito.viewmodels.HistoryViewModel
28+
import kotlinx.coroutines.launch
2529

2630
private val Orange = Color(0xFFFF6C00)
2731
private val OrangeLight = Color(0xFFFFF3EC)
@@ -138,16 +142,25 @@ private fun GlobalScoreCard(taken: Int, total: Int) {
138142
color = Color(0xFF888888)
139143
)
140144
}
145+
val animatedProgress = remember { Animatable(0f) }
146+
147+
LaunchedEffect(ratio) {
148+
animatedProgress.animateTo(
149+
targetValue = ratio,
150+
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing)
151+
)
152+
}
153+
141154
Box(contentAlignment = Alignment.Center) {
142155
CircularProgressIndicator(
143-
progress = { ratio },
144-
modifier = Modifier.size(72.dp),
145-
strokeWidth = 7.dp,
146-
color = Green,
147-
trackColor = Red.copy(alpha = 0.25f)
156+
progress = { animatedProgress.value },
157+
modifier = Modifier.size(72.dp),
158+
strokeWidth = 7.dp,
159+
color = Green,
160+
trackColor = Red.copy(alpha = 0.25f)
148161
)
149162
Text(
150-
text = "${(ratio * 100).toInt()}%",
163+
text = "${(animatedProgress.value * 100).toInt()}%",
151164
fontSize = 15.sp,
152165
fontWeight = FontWeight.Bold,
153166
color = Color(0xFF333333)
@@ -237,9 +250,19 @@ private fun DayBar(day: DayStats, maxBarHeight: Float) {
237250
.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp)),
238251
contentAlignment = Alignment.BottomCenter
239252
) {
253+
val animatedPendingH = remember { Animatable(0f) }
254+
val animatedMissedH = remember { Animatable(0f) }
255+
val animatedTakenH = remember { Animatable(0f) }
256+
257+
LaunchedEffect(day) {
258+
launch { animatedPendingH.animateTo(pendingH.value, tween(800, easing = FastOutSlowInEasing)) }
259+
launch { animatedMissedH.animateTo(missedH.value, tween(800, easing = FastOutSlowInEasing)) }
260+
launch { animatedTakenH.animateTo(takenH.value, tween(800, easing = FastOutSlowInEasing)) }
261+
}
262+
240263
Column(
241-
modifier = Modifier.fillMaxSize(),
242-
verticalArrangement = Arrangement.Bottom
264+
verticalArrangement = Arrangement.Bottom,
265+
modifier = Modifier.fillMaxHeight()
243266
) {
244267
if (day.total == 0) {
245268
Box(
@@ -253,24 +276,24 @@ private fun DayBar(day: DayStats, maxBarHeight: Float) {
253276
Box(
254277
modifier = Modifier
255278
.fillMaxWidth()
256-
.height(pendingH)
279+
.height(animatedPendingH.value.dp)
257280
.background(Grey)
258281
)
259282
}
260283
if (day.missed > 0) {
261284
Box(
262285
modifier = Modifier
263286
.fillMaxWidth()
264-
.height(missedH)
287+
.height(animatedMissedH.value.dp)
265288
.background(Red)
266289
)
267290
}
268291
if (day.taken > 0) {
269292
Box(
270293
modifier = Modifier
271294
.fillMaxWidth()
272-
.height(takenH)
273-
.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
295+
.height(animatedTakenH.value.dp)
296+
//.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
274297
.background(Green)
275298
)
276299
}
@@ -327,22 +350,39 @@ private fun DrugStatRow(stat: DrugStat) {
327350
)
328351
}
329352

353+
val total = stat.total.coerceAtLeast(1)
354+
val takenTarget = stat.taken.toFloat() / total
355+
val missedTarget = stat.missed.toFloat() / total
356+
val pendingTarget = stat.pending.toFloat() / total
357+
358+
val animatedTaken = remember { Animatable(0f) }
359+
val animatedMissed = remember { Animatable(0f) }
360+
val animatedPending = remember { Animatable(0f) }
361+
362+
LaunchedEffect(stat) {
363+
launch { animatedTaken.animateTo(takenTarget, tween(800, easing = FastOutSlowInEasing)) }
364+
launch { animatedMissed.animateTo(missedTarget, tween(800, easing = FastOutSlowInEasing)) }
365+
launch { animatedPending.animateTo(pendingTarget, tween(800, easing = FastOutSlowInEasing)) }
366+
}
367+
330368
Row(
331369
modifier = Modifier
332370
.fillMaxWidth()
333371
.height(10.dp)
334372
.clip(RoundedCornerShape(50))
335373
) {
336-
val total = stat.total.coerceAtLeast(1)
337-
val takenW = stat.taken.toFloat() / total
338-
val missedW = stat.missed.toFloat() / total
339-
val pendingW = stat.pending.toFloat() / total
374+
if (stat.taken > 0)
375+
Box(Modifier.weight(animatedTaken.value.coerceAtLeast(0.0001f)).fillMaxHeight().background(Green))
376+
if (stat.missed > 0)
377+
Box(Modifier.weight(animatedMissed.value.coerceAtLeast(0.0001f)).fillMaxHeight().background(Red))
378+
if (stat.pending > 0)
379+
Box(Modifier.weight(animatedPending.value.coerceAtLeast(0.0001f)).fillMaxHeight().background(Grey))
340380

341-
if (stat.taken > 0) Box(Modifier.weight(takenW).fillMaxHeight().background(Green))
342-
if (stat.missed > 0) Box(Modifier.weight(missedW).fillMaxHeight().background(Red))
343-
if (stat.pending > 0) Box(Modifier.weight(pendingW).fillMaxHeight().background(Grey))
381+
val remainingWeight = (1f - animatedTaken.value - animatedMissed.value - animatedPending.value).coerceAtLeast(0.0001f)
382+
Box(Modifier.weight(remainingWeight).fillMaxHeight().background(Color(0xFFEEEEEE)))
344383

345-
if (stat.total == 0) Box(Modifier.weight(1f).fillMaxHeight().background(Color(0xFFEEEEEE)))
384+
if (stat.total == 0)
385+
Box(Modifier.weight(1f).fillMaxHeight().background(Color(0xFFEEEEEE)))
346386
}
347387

348388
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {

app/src/main/java/fr/utbm/sy43/pilulito/ui/screens/TodayScreen.kt

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
package fr.utbm.sy43.pilulito.ui.screens
22

3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.animation.fadeIn
6+
import androidx.compose.animation.slideInHorizontally
37
import androidx.compose.foundation.layout.Column
48
import androidx.compose.foundation.layout.fillMaxWidth
59
import androidx.compose.foundation.layout.padding
610
import androidx.compose.foundation.lazy.LazyColumn
711
import androidx.compose.foundation.lazy.items
12+
import androidx.compose.foundation.lazy.itemsIndexed
813
import androidx.compose.foundation.shape.RoundedCornerShape
914
import androidx.compose.material3.AlertDialog
1015
import androidx.compose.material3.CircularProgressIndicator
1116
import androidx.compose.material3.Text
1217
import androidx.compose.material3.TextButton
1318
import androidx.compose.runtime.Composable
19+
import androidx.compose.runtime.LaunchedEffect
1420
import androidx.compose.runtime.collectAsState
1521
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
1625
import androidx.compose.ui.Alignment
1726
import androidx.compose.ui.Modifier
1827
import androidx.compose.ui.graphics.Color
@@ -29,6 +38,7 @@ import fr.utbm.sy43.pilulito.ui.components.OverdueIntakeComponent
2938
import fr.utbm.sy43.pilulito.ui.components.TopBarComponent
3039
import fr.utbm.sy43.pilulito.ui.theme.SenPosTheme
3140
import fr.utbm.sy43.pilulito.viewmodels.HomeViewModel
41+
import kotlinx.coroutines.delay
3242

3343
@Composable
3444
fun TodayScreen(
@@ -83,14 +93,27 @@ fun TodayScreen(
8393
)
8494
} else {
8595
LazyColumn(modifier = Modifier.weight(1f)) {
86-
items(
87-
items = uiState.todayIntakes,
88-
key = { it.intakeId }
89-
) { intake ->
90-
IntakeComponent(
91-
intake = intake,
92-
onTake = { viewModel.markAsTaken(it) }
93-
)
96+
97+
itemsIndexed(items = uiState.todayIntakes) { index, intake ->
98+
var visible by remember { mutableStateOf(false) }
99+
100+
LaunchedEffect(Unit) {
101+
delay(index * 100L)
102+
visible = true
103+
}
104+
105+
AnimatedVisibility(
106+
visible = visible,
107+
enter = slideInHorizontally(
108+
initialOffsetX = { fullWidth -> -fullWidth },
109+
animationSpec = tween(durationMillis = 400)
110+
) + fadeIn(animationSpec = tween(durationMillis = 400))
111+
) {
112+
IntakeComponent(
113+
intake = intake,
114+
onTake = { viewModel.markAsTaken(it) }
115+
)
116+
}
94117
}
95118

96119
if (uiState.todayIntakes.isEmpty()) {

0 commit comments

Comments
 (0)