Skip to content

Commit a893eed

Browse files
committed
WTA #71 moved logic to flip card/chip to separate component and added for card as well
1 parent 0bf4159 commit a893eed

File tree

5 files changed

+132
-71
lines changed

5 files changed

+132
-71
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.jacob.wakatimeapp.core.ui.components
2+
3+
import androidx.compose.animation.core.animateFloatAsState
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.MutableState
6+
import androidx.compose.runtime.State
7+
import androidx.compose.runtime.mutableStateOf
8+
import androidx.compose.runtime.remember
9+
10+
/**
11+
* [Rotating on Axis in 3D](https://www.youtube.com/watch?v=WdQUDHOwlgE&t=148s)
12+
* [Resetting Animation for each click](https://stackoverflow.com/questions/78620347/repeat-animation-when-user-clicks-jetpack-compose-android)
13+
* [Showing the back of the card correctly](https://medium.com/bilue/card-flip-animation-with-jetpack-compose-f60aaaad4ac9)
14+
*/
15+
@Composable
16+
fun <T> rememberFlippableState(
17+
frontContent: @Composable T.() -> Unit = {},
18+
backContent: @Composable T.() -> Unit = {},
19+
): FlippableElementData<T> {
20+
val isFlipped = remember { mutableStateOf(false) }
21+
val rotationXAnimation = animateFloatAsState(targetValue = if (isFlipped.value) 180f else 0f)
22+
23+
val contentToShow = remember(rotationXAnimation.value) {
24+
if (rotationXAnimation.value < 90f) frontContent else backContent
25+
}
26+
27+
val innerRotation = remember(rotationXAnimation.value) {
28+
if (rotationXAnimation.value < 90f) 0f else 180f
29+
}
30+
31+
return FlippableElementData(
32+
isFlipped = isFlipped,
33+
rotationAnimationValue = rotationXAnimation,
34+
contentToShow = contentToShow,
35+
innerRotation = innerRotation,
36+
)
37+
}
38+
39+
class FlippableElementData<T>(
40+
val isFlipped: MutableState<Boolean>,
41+
val rotationAnimationValue: State<Float>,
42+
val contentToShow: @Composable T.() -> Unit,
43+
val innerRotation: Float,
44+
)

core/ui/src/main/java/com/jacob/wakatimeapp/core/ui/components/cards/StatsCard.kt

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,32 @@ import androidx.compose.foundation.layout.fillMaxWidth
1010
import androidx.compose.foundation.layout.padding
1111
import androidx.compose.foundation.layout.size
1212
import androidx.compose.foundation.shape.RoundedCornerShape
13+
import androidx.compose.material3.MaterialTheme
1314
import androidx.compose.runtime.Composable
1415
import androidx.compose.ui.Alignment
1516
import androidx.compose.ui.Modifier
1617
import androidx.compose.ui.draw.shadow
1718
import androidx.compose.ui.graphics.Brush
19+
import androidx.compose.ui.graphics.ColorFilter
20+
import androidx.compose.ui.graphics.graphicsLayer
1821
import androidx.compose.ui.layout.ContentScale
1922
import androidx.compose.ui.res.painterResource
2023
import androidx.compose.ui.unit.Dp
2124
import androidx.compose.ui.unit.dp
25+
import com.jacob.wakatimeapp.core.ui.components.rememberFlippableState
26+
import com.jacob.wakatimeapp.core.ui.theme.assets
2227
import com.jacob.wakatimeapp.core.ui.theme.colors.Gradient
2328

2429
@Composable
2530
fun StatsCard(
2631
gradient: Gradient,
2732
onClick: () -> Unit,
28-
roundedCornerPercent: Int = 25,
2933
@DrawableRes iconId: Int,
34+
modifier: Modifier = Modifier,
35+
roundedCornerPercent: Int = 25,
3036
iconOffset: Dp = 50.dp,
3137
iconSize: Dp = 80.dp,
38+
rotation: Float = 0f,
3239
cardContent: @Composable BoxScope.() -> Unit = {},
3340
) {
3441
val cardGradient = Brush.horizontalGradient(
@@ -40,22 +47,49 @@ fun StatsCard(
4047
val cardShape = RoundedCornerShape(roundedCornerPercent)
4148
Box(
4249
contentAlignment = Alignment.Center,
43-
modifier = Modifier
50+
modifier = modifier
4451
.shadow(elevation = 10.dp, shape = cardShape)
4552
.clickable(onClick = onClick)
4653
.fillMaxWidth()
4754
.background(cardGradient, cardShape),
4855
content = {
49-
5056
Image(
5157
painter = painterResource(id = iconId),
5258
contentDescription = "",
5359
contentScale = ContentScale.FillBounds,
60+
colorFilter = ColorFilter.tint(gradient.onEndColor),
61+
alpha = 0.15f,
5462
modifier = Modifier
5563
.padding(start = iconOffset)
56-
.size(iconSize),
64+
.size(iconSize)
65+
.graphicsLayer { this.rotationX = rotation },
5766
)
58-
cardContent()
67+
Box(Modifier.graphicsLayer { this.rotationX = rotation }, content = cardContent)
68+
},
69+
)
70+
}
71+
72+
@Composable
73+
fun FlippableStatsCard(
74+
gradient: Gradient,
75+
modifier: Modifier = Modifier,
76+
iconId: Int = MaterialTheme.assets.icons.time,
77+
roundedCornerPercent: Int = 25,
78+
frontContent: @Composable BoxScope.() -> Unit = {},
79+
backContent: @Composable BoxScope.() -> Unit = {},
80+
) {
81+
val flippableState = rememberFlippableState(frontContent, backContent)
82+
83+
StatsCard(
84+
gradient = gradient,
85+
iconId = iconId,
86+
roundedCornerPercent = roundedCornerPercent,
87+
onClick = { flippableState.isFlipped.value = !flippableState.isFlipped.value },
88+
modifier = modifier.graphicsLayer {
89+
this.rotationX = flippableState.rotationAnimationValue.value
90+
this.cameraDistance = 8 * this.density
5991
},
92+
rotation = flippableState.innerRotation,
93+
cardContent = flippableState.contentToShow,
6094
)
6195
}

core/ui/src/main/java/com/jacob/wakatimeapp/core/ui/components/cards/StatsChip.kt

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.jacob.wakatimeapp.core.ui.components.cards
22

33
import androidx.annotation.DrawableRes
4-
import androidx.compose.animation.core.animateFloatAsState
54
import androidx.compose.foundation.Image
65
import androidx.compose.foundation.background
76
import androidx.compose.foundation.clickable
@@ -17,10 +16,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
1716
import androidx.compose.material3.MaterialTheme
1817
import androidx.compose.material3.Surface
1918
import androidx.compose.runtime.Composable
20-
import androidx.compose.runtime.getValue
21-
import androidx.compose.runtime.mutableStateOf
22-
import androidx.compose.runtime.remember
23-
import androidx.compose.runtime.setValue
2419
import androidx.compose.ui.Alignment
2520
import androidx.compose.ui.Modifier
2621
import androidx.compose.ui.draw.clip
@@ -30,6 +25,7 @@ import androidx.compose.ui.graphics.graphicsLayer
3025
import androidx.compose.ui.res.painterResource
3126
import androidx.compose.ui.unit.dp
3227
import com.jacob.wakatimeapp.core.ui.WtaPreviews
28+
import com.jacob.wakatimeapp.core.ui.components.rememberFlippableState
3329
import com.jacob.wakatimeapp.core.ui.theme.WakaTimeAppTheme
3430
import com.jacob.wakatimeapp.core.ui.theme.assets
3531
import com.jacob.wakatimeapp.core.ui.theme.colors.Gradient
@@ -60,6 +56,7 @@ fun StatsChip(
6056
painter = painterResource(iconId),
6157
contentDescription = null,
6258
colorFilter = ColorFilter.tint(gradient.onEndColor),
59+
alpha = 0.2f,
6360
modifier = Modifier
6461
.padding(
6562
end = MaterialTheme.spacing.small,
@@ -83,41 +80,27 @@ fun StatsChip(
8380
}
8481
}
8582

86-
/**
87-
* [Rotating on Axis in 3D](https://www.youtube.com/watch?v=WdQUDHOwlgE&t=148s)
88-
* [Resetting Animation for each click](https://stackoverflow.com/questions/78620347/repeat-animation-when-user-clicks-jetpack-compose-android)
89-
* [Showing the back of the card correctly](https://medium.com/bilue/card-flip-animation-with-jetpack-compose-f60aaaad4ac9)
90-
*/
9183
@Composable
92-
fun InteractableStatsChip(
84+
fun FlippableStatsChip(
9385
modifier: Modifier,
9486
gradient: Gradient,
9587
iconId: Int = MaterialTheme.assets.icons.time,
9688
frontContent: @Composable ColumnScope.() -> Unit = {},
9789
backContent: @Composable ColumnScope.() -> Unit = {},
9890
) {
99-
var isFlipped by remember { mutableStateOf(false) }
100-
val rotationXAnimation = animateFloatAsState(targetValue = if (isFlipped) 180f else 0f)
101-
102-
val contentToShow = remember(rotationXAnimation.value) {
103-
if (rotationXAnimation.value < 90f) frontContent else backContent
104-
}
105-
106-
val innerRotation = remember(rotationXAnimation.value) {
107-
if (rotationXAnimation.value < 90f) 0f else 180f
108-
}
91+
val flippableState = rememberFlippableState(frontContent, backContent)
10992

11093
StatsChip(
11194
gradient = gradient,
11295
iconId = iconId,
11396
roundedCornerPercent = 15,
114-
onClick = { isFlipped = !isFlipped },
97+
onClick = { flippableState.isFlipped.value = !flippableState.isFlipped.value },
11598
modifier = modifier.graphicsLayer {
116-
this.rotationX = rotationXAnimation.value
99+
this.rotationX = flippableState.rotationAnimationValue.value
117100
this.cameraDistance = 8 * this.density
118101
},
119-
rotation = innerRotation,
120-
chipContent = { contentToShow() },
102+
rotation = flippableState.innerRotation,
103+
chipContent = flippableState.contentToShow,
121104
)
122105
}
123106

details/src/main/java/com/jacob/wakatimeapp/details/ui/components/ProjectStreakChips.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import androidx.compose.runtime.Composable
99
import androidx.compose.ui.Modifier
1010
import androidx.compose.ui.text.font.FontWeight
1111
import com.jacob.wakatimeapp.core.models.Streak
12-
import com.jacob.wakatimeapp.core.ui.components.cards.InteractableStatsChip
12+
import com.jacob.wakatimeapp.core.ui.components.cards.FlippableStatsChip
13+
import com.jacob.wakatimeapp.core.ui.theme.assets
14+
import com.jacob.wakatimeapp.core.ui.theme.colors.Gradient
1315
import com.jacob.wakatimeapp.core.ui.theme.gradients
1416
import com.jacob.wakatimeapp.core.ui.theme.spacing
1517
import com.jacob.wakatimeapp.details.ui.DetailsPageViewState
@@ -20,9 +22,10 @@ internal fun ProjectStreakChips(detailsPageData: DetailsPageViewState.Loaded, mo
2022
modifier = modifier.fillMaxWidth(),
2123
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small),
2224
) {
23-
InteractableStatsChip(
25+
FlippableStatsChip(
2426
modifier = Modifier.weight(1f),
2527
gradient = MaterialTheme.gradients.shifter,
28+
iconId = MaterialTheme.assets.icons.longestStreak,
2629
frontContent = {
2730
ChipContent(
2831
cardHeading = "${detailsPageData.longestStreakInProject.days} Days",
@@ -31,28 +34,33 @@ internal fun ProjectStreakChips(detailsPageData: DetailsPageViewState.Loaded, mo
3134
statValueTextStyle = MaterialTheme.typography.titleMedium,
3235
)
3336
},
34-
backContent = { StreakRangeDisplay(detailsPageData.longestStreakInProject) },
37+
backContent = {
38+
StreakRangeDisplay(
39+
detailsPageData.longestStreakInProject,
40+
MaterialTheme.gradients.shifter,
41+
)
42+
},
3543
)
3644

37-
InteractableStatsChip(
45+
FlippableStatsChip(
3846
modifier = Modifier.weight(1f),
39-
gradient = MaterialTheme.gradients.shifter,
47+
gradient = MaterialTheme.gradients.reef,
48+
iconId = MaterialTheme.assets.icons.streak,
4049
frontContent = {
4150
ChipContent(
4251
cardHeading = "${detailsPageData.currentStreakInProject.days} Days",
4352
cardSubHeading = "Current Streak",
44-
gradient = MaterialTheme.gradients.shifter,
53+
gradient = MaterialTheme.gradients.reef,
4554
statValueTextStyle = MaterialTheme.typography.titleMedium,
4655
)
4756
},
48-
backContent = { StreakRangeDisplay(detailsPageData.currentStreakInProject) },
57+
backContent = { StreakRangeDisplay(detailsPageData.currentStreakInProject, MaterialTheme.gradients.reef) },
4958
)
5059
}
5160
}
5261

5362
@Composable
54-
private fun StreakRangeDisplay(streak: Streak) {
55-
val gradient = MaterialTheme.gradients.shifter
63+
private fun StreakRangeDisplay(streak: Streak, gradient: Gradient) {
5664
Text(
5765
text = streak.formattedPrintRange(),
5866
color = gradient.onStartColor,

details/src/main/java/com/jacob/wakatimeapp/details/ui/components/QuickStatsCards.kt

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
77
import androidx.compose.foundation.layout.fillMaxWidth
88
import androidx.compose.material3.MaterialTheme
99
import androidx.compose.material3.Surface
10-
import androidx.compose.material3.Text
1110
import androidx.compose.runtime.Composable
1211
import androidx.compose.ui.Modifier
1312
import com.jacob.wakatimeapp.core.models.Time
@@ -16,8 +15,8 @@ import com.jacob.wakatimeapp.core.models.secondarystats.Languages
1615
import com.jacob.wakatimeapp.core.models.secondarystats.Machines
1716
import com.jacob.wakatimeapp.core.models.secondarystats.OperatingSystems
1817
import com.jacob.wakatimeapp.core.ui.WtaPreviews
19-
import com.jacob.wakatimeapp.core.ui.components.cards.InteractableStatsChip
20-
import com.jacob.wakatimeapp.core.ui.components.cards.StatsCard
18+
import com.jacob.wakatimeapp.core.ui.components.cards.FlippableStatsCard
19+
import com.jacob.wakatimeapp.core.ui.components.cards.FlippableStatsChip
2120
import com.jacob.wakatimeapp.core.ui.theme.WakaTimeAppTheme
2221
import com.jacob.wakatimeapp.core.ui.theme.assets
2322
import com.jacob.wakatimeapp.core.ui.theme.gradients
@@ -49,40 +48,33 @@ internal fun QuickStatsCards(detailsPageData: DetailsPageViewState.Loaded) {
4948
Modifier.fillMaxWidth(),
5049
Arrangement.spacedBy(MaterialTheme.spacing.sMedium),
5150
) {
52-
// TODO: MERGE BELOW 2 CARDS INTO 1 AND MAKE THEN INTERACTABLE, CARDS SWAP WHEN CLICKED
53-
StatsCard(
54-
gradient = MaterialTheme.gradients.amin,
55-
roundedCornerPercent = 15,
56-
iconId = MaterialTheme.assets.icons.time,
57-
onClick = {},
58-
) {
59-
CardContent(
60-
cardSubHeading = "Total Time on Project",
61-
cardHeading = detailsPageData.totalTime.formattedPrint(),
62-
gradient = MaterialTheme.gradients.amin,
63-
)
64-
}
65-
66-
StatsCard(
51+
FlippableStatsCard(
6752
gradient = MaterialTheme.gradients.purpink,
6853
roundedCornerPercent = 15,
6954
iconId = MaterialTheme.assets.icons.time,
70-
onClick = {},
71-
) {
72-
CardContent(
73-
cardSubHeading = "Average Time",
74-
cardHeading = detailsPageData.averageTime.formattedPrint(),
75-
gradient = MaterialTheme.gradients.purpink,
76-
)
77-
}
55+
frontContent = {
56+
CardContent(
57+
cardSubHeading = "Total Time on Project",
58+
cardHeading = detailsPageData.totalTime.formattedPrint(),
59+
gradient = MaterialTheme.gradients.purpink,
60+
)
61+
},
62+
backContent = {
63+
CardContent(
64+
cardSubHeading = "Average Time",
65+
cardHeading = detailsPageData.averageTime.formattedPrint(),
66+
gradient = MaterialTheme.gradients.purpink,
67+
)
68+
},
69+
)
7870

7971
ProjectStreakChips(detailsPageData)
8072

8173
Row(
8274
modifier = Modifier.fillMaxWidth(),
8375
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small),
8476
) {
85-
InteractableStatsChip(
77+
FlippableStatsChip(
8678
modifier = Modifier.weight(1f),
8779
gradient = MaterialTheme.gradients.quepal,
8880
frontContent = {
@@ -102,7 +94,7 @@ internal fun QuickStatsCards(detailsPageData: DetailsPageViewState.Loaded) {
10294
)
10395
},
10496
)
105-
InteractableStatsChip(
97+
FlippableStatsChip(
10698
modifier = Modifier.weight(1f),
10799
gradient = MaterialTheme.gradients.flare,
108100
frontContent = {
@@ -135,10 +127,10 @@ internal fun QuickStatsCards(detailsPageData: DetailsPageViewState.Loaded) {
135127
// after clicking/expanding
136128
// 🟥🟥
137129
// 🟥🟥
138-
Text("Most used language")
139-
Text("Most used editor")
140-
Text("Most used os")
141-
Text("Most used machine")
130+
// Text("Most used language")
131+
// Text("Most used editor")
132+
// Text("Most used os")
133+
// Text("Most used machine")
142134
}
143135
}
144136
}

0 commit comments

Comments
 (0)