Skip to content

Commit aa14d09

Browse files
authored
Merge pull request #100 from cuappdev/helen/cap-reminder-tutorial
Implement capacity reminder tutorial + loading state
2 parents 2904b71 + 7cb5155 commit aa14d09

10 files changed

Lines changed: 565 additions & 91 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ dependencies {
8181
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
8282
implementation 'androidx.compose.material:material:1.7.8'
8383
implementation('androidx.compose.material3:material3-android:1.3.1')
84+
implementation 'androidx.compose.foundation:foundation-layout:1.9.4'
85+
implementation 'androidx.compose.animation:animation-core:1.9.4'
8486
testImplementation 'junit:junit:4.13.2'
8587
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
8688
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
@@ -111,7 +113,6 @@ dependencies {
111113
implementation 'com.google.firebase:firebase-auth'
112114
implementation 'com.google.firebase:firebase-messaging'
113115
implementation("com.google.firebase:firebase-analytics")
114-
115116
}
116117

117118
apollo {

app/src/main/java/com/cornellappdev/uplift/data/repositories/DatastoreRepository.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.cornellappdev.uplift.util.CLASS_FAVORITES_KEY
1111
import com.cornellappdev.uplift.util.GYM_FAVORITES_KEY
1212
import kotlinx.coroutines.CoroutineScope
1313
import kotlinx.coroutines.Dispatchers
14+
import kotlinx.coroutines.flow.Flow
1415
import kotlinx.coroutines.flow.SharingStarted
1516
import kotlinx.coroutines.flow.firstOrNull
1617
import kotlinx.coroutines.flow.map
@@ -33,6 +34,7 @@ object PreferencesKeys {
3334
val CAPACITY_REMINDERS_PERCENT = intPreferencesKey("capacityRemindersPercent")
3435
val CAPACITY_REMINDERS_SELECTED_DAYS = stringSetPreferencesKey("capacityRemindersSelectedDays")
3536
val CAPACITY_REMINDERS_SELECTED_GYMS = stringSetPreferencesKey("capacityRemindersSelectedGyms")
37+
val CAPACITY_REMINDERS_TUTORIAL_SHOWN = booleanPreferencesKey("capacityReminderTutorialShown")
3638
}
3739

3840
@Singleton
@@ -112,4 +114,10 @@ class DatastoreRepository @Inject constructor(private val dataStore: DataStore<P
112114
}
113115
}
114116
}
117+
118+
fun hasShownCapacityTutorial(): Flow<Boolean> {
119+
return dataStore.data.map { preferences ->
120+
preferences[PreferencesKeys.CAPACITY_REMINDERS_TUTORIAL_SHOWN] ?: false
121+
}
122+
}
115123
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package com.cornellappdev.uplift.ui.components.capacityreminder
2+
3+
import androidx.compose.animation.core.EaseInOut
4+
import androidx.compose.animation.core.LinearEasing
5+
import androidx.compose.animation.core.RepeatMode
6+
import androidx.compose.animation.core.VectorConverter
7+
import androidx.compose.animation.core.animateFloat
8+
import androidx.compose.animation.core.animateValue
9+
import androidx.compose.animation.core.infiniteRepeatable
10+
import androidx.compose.animation.core.rememberInfiniteTransition
11+
import androidx.compose.animation.core.tween
12+
import androidx.compose.foundation.Canvas
13+
import androidx.compose.foundation.Image
14+
import androidx.compose.foundation.background
15+
import androidx.compose.foundation.layout.Box
16+
import androidx.compose.foundation.layout.fillMaxSize
17+
import androidx.compose.foundation.layout.fillMaxWidth
18+
import androidx.compose.foundation.layout.height
19+
import androidx.compose.foundation.layout.offset
20+
import androidx.compose.foundation.layout.padding
21+
import androidx.compose.foundation.layout.size
22+
import androidx.compose.foundation.layout.width
23+
import androidx.compose.foundation.shape.RoundedCornerShape
24+
import androidx.compose.material3.Text
25+
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.getValue
27+
import androidx.compose.ui.Alignment
28+
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.draw.shadow
30+
import androidx.compose.ui.geometry.Offset
31+
import androidx.compose.ui.graphics.Brush
32+
import androidx.compose.ui.graphics.Color
33+
import androidx.compose.ui.graphics.StrokeCap
34+
import androidx.compose.ui.layout.ContentScale
35+
import androidx.compose.ui.res.painterResource
36+
import androidx.compose.ui.text.font.FontWeight
37+
import androidx.compose.ui.text.style.TextAlign
38+
import androidx.compose.ui.tooling.preview.Preview
39+
import androidx.compose.ui.unit.Dp
40+
import androidx.compose.ui.unit.dp
41+
import androidx.compose.ui.unit.sp
42+
import com.cornellappdev.uplift.R
43+
import com.cornellappdev.uplift.util.PRIMARY_BLACK
44+
import com.cornellappdev.uplift.util.montserratFamily
45+
import kotlin.math.cos
46+
import kotlin.math.sin
47+
48+
49+
@Composable
50+
51+
fun CapacityReminderLoading() {
52+
val infiniteTransition = rememberInfiniteTransition(label = "Capacity Reminder Loading")
53+
54+
val rotationAngle by infiniteTransition.animateFloat(
55+
initialValue = 0f,
56+
targetValue = 360f, animationSpec = infiniteRepeatable(
57+
animation = tween(durationMillis = 2000, easing = LinearEasing),
58+
repeatMode = RepeatMode.Restart
59+
),
60+
label = "rotationAngle"
61+
)
62+
63+
val logoYOffset by infiniteTransition.animateValue(
64+
initialValue = 0.dp,
65+
targetValue = (-20).dp, animationSpec = infiniteRepeatable(
66+
animation = tween(durationMillis = 750, easing = EaseInOut),
67+
repeatMode = RepeatMode.Reverse
68+
),
69+
label = "logoYOffset",
70+
typeConverter = Dp.VectorConverter
71+
)
72+
73+
val componentSize by infiniteTransition.animateFloat(
74+
initialValue = 1f,
75+
targetValue = 0.97f, animationSpec = infiniteRepeatable(
76+
animation = tween(durationMillis = 750, easing = EaseInOut),
77+
repeatMode = RepeatMode.Reverse
78+
),
79+
label = "componentSize"
80+
)
81+
82+
val textOpacity by infiniteTransition.animateFloat(
83+
initialValue = 1f,
84+
targetValue = 0.5f, animationSpec = infiniteRepeatable(
85+
animation = tween(durationMillis = 750, easing = EaseInOut),
86+
repeatMode = RepeatMode.Reverse
87+
),
88+
label = "textOpacity"
89+
)
90+
91+
val dotCount by infiniteTransition.animateValue(
92+
initialValue = 1,
93+
targetValue = 4, // Animate up to (but not including) this value
94+
typeConverter = Int.VectorConverter,
95+
animationSpec = infiniteRepeatable(
96+
animation = tween(
97+
durationMillis = 1500,
98+
easing = LinearEasing
99+
),
100+
repeatMode = RepeatMode.Restart
101+
),
102+
label = "dotCount"
103+
)
104+
105+
Box(
106+
modifier = Modifier
107+
.fillMaxSize()
108+
.background(Color(0xFFD9D9D9).copy(alpha = 0.5f))
109+
) {
110+
Box(
111+
modifier = Modifier
112+
.height((270 * componentSize).toInt().dp)
113+
.width((249 * componentSize).toInt().dp)
114+
.shadow(
115+
elevation = 40.dp,
116+
shape = RoundedCornerShape(20.dp),
117+
ambientColor = Color(0xFFE5ECED),
118+
spotColor = Color(0xFFE5ECED)
119+
)
120+
.background(PRIMARY_BLACK, RoundedCornerShape(20.dp))
121+
.align(Alignment.Center)
122+
) {
123+
Box(
124+
modifier = Modifier
125+
.padding(vertical = 25.dp, horizontal = 20.dp)
126+
.fillMaxSize()
127+
) {
128+
Text(
129+
text = buildString {
130+
append("Loading")
131+
repeat(dotCount) { append(".") }
132+
},
133+
fontSize = 24.sp,
134+
fontFamily = montserratFamily,
135+
fontWeight = FontWeight(500),
136+
color = Color.White.copy(alpha = textOpacity),
137+
textAlign = TextAlign.Center,
138+
modifier = Modifier
139+
.fillMaxWidth()
140+
.align(Alignment.BottomCenter)
141+
)
142+
143+
Box(
144+
modifier = Modifier.fillMaxWidth(),
145+
contentAlignment = Alignment.BottomCenter
146+
) {
147+
Canvas(modifier = Modifier.size(100.dp)) {
148+
val center = size / 2f
149+
val rayLength = size.minDimension
150+
val rayCount = 8
151+
val strokeWidth = 8.dp.toPx()
152+
153+
for (i in 0 until rayCount) {
154+
val angle = i * (360f / rayCount) - 90f + rotationAngle
155+
val radians = Math.toRadians(angle.toDouble())
156+
val start = Offset(
157+
center.width + (rayLength * 0.50f * cos(radians)).toFloat(),
158+
center.height + (rayLength * 0.50f * sin(radians)).toFloat()
159+
)
160+
val end = Offset(
161+
center.width + (rayLength * 0.7 * cos(radians)).toFloat(),
162+
center.height + (rayLength * 0.7 * sin(radians)).toFloat()
163+
)
164+
val brush = Brush.radialGradient(
165+
colorStops = arrayOf(
166+
0.0f to Color(0xFFFFE894).copy(alpha = 1f - (i * 0.1f)),
167+
168+
1.0f to Color(0xffffffff).copy(alpha = 1f - (i * 0.1f))
169+
),
170+
center = Offset(
171+
0.1464f,
172+
0.8418f
173+
),
174+
radius = 0.6965f
175+
)
176+
177+
drawLine(
178+
brush = brush,
179+
start = start,
180+
end = end,
181+
strokeWidth = strokeWidth,
182+
cap = StrokeCap.Round
183+
)
184+
}
185+
}
186+
Image(
187+
painter = painterResource(R.drawable.ic_logo_sun_var),
188+
contentDescription = "Uplift logo",
189+
modifier = Modifier
190+
.width(135.dp)
191+
.offset(y = 25.dp + logoYOffset),
192+
contentScale = ContentScale.FillWidth
193+
)
194+
}
195+
}
196+
}
197+
}
198+
}
199+
200+
@Preview
201+
@Composable
202+
fun CapacityReminderLoadingPreview() {
203+
CapacityReminderLoading()
204+
}

0 commit comments

Comments
 (0)