From f7bf25226e77b6f16325cf8680bd7a00e66b3818 Mon Sep 17 00:00:00 2001 From: Melissa Velasquez Date: Sun, 8 Mar 2026 00:53:15 -0500 Subject: [PATCH 1/6] Updated schema and User graphqls --- app/src/main/graphql/User.graphql | 14 ++++- app/src/main/graphql/schema.graphqls | 93 ++++++++++++++++++---------- 2 files changed, 72 insertions(+), 35 deletions(-) diff --git a/app/src/main/graphql/User.graphql b/app/src/main/graphql/User.graphql index b3765527..764ea9d9 100644 --- a/app/src/main/graphql/User.graphql +++ b/app/src/main/graphql/User.graphql @@ -1,14 +1,24 @@ fragment userFields on User { id email - name netId + name + encodedImage + activeStreak + maxStreak + streakStart + workoutGoal + lastGoalChange + lastStreak + totalGymDays } fragment workoutFields on Workout { id workoutTime userId + facilityId + gymName } mutation CreateUser($email: String!, $name: String!, $netId: String!) { @@ -33,7 +43,7 @@ query getUserByNetId($netId: String!) { } } -mutation SetWorkoutGoals($id: Int!, $workoutGoal: [String!]!) { +mutation SetWorkoutGoals($id: Int!, $workoutGoal: Int!) { setWorkoutGoals(userId: $id, workoutGoal: $workoutGoal) { ...userFields } diff --git a/app/src/main/graphql/schema.graphqls b/app/src/main/graphql/schema.graphqls index 9df7f3a7..2b3e050b 100644 --- a/app/src/main/graphql/schema.graphqls +++ b/app/src/main/graphql/schema.graphqls @@ -36,16 +36,6 @@ type Query { """ getAllReports: [Report] - """ - Get the workout goals of a user by ID. - """ - getWorkoutGoals(id: Int!): [String] - - """ - Get the current and max workout streak of a user. - """ - getUserStreak(id: Int!): JSONString - """ Get all facility hourly average capacities. """ @@ -358,16 +348,22 @@ type User { name: String! - activeStreak: Int + activeStreak: Int! + + maxStreak: Int! + + workoutGoal: Int - maxStreak: Int + lastGoalChange: DateTime - workoutGoal: [DayOfWeekGraphQLEnum] + lastStreak: Int! encodedImage: String giveaways: [Giveaway] + goalHistory: [WorkoutGoalHistory] + friendRequestsSent: [Friendship] friendRequestsReceived: [Friendship] @@ -375,22 +371,16 @@ type User { friendships: [Friendship] friends: [User] -} - -enum DayOfWeekGraphQLEnum { - MONDAY - - TUESDAY - - WEDNESDAY - - THURSDAY - - FRIDAY - SATURDAY + """ + Get the total number of gym days (unique workout days) for user. + """ + totalGymDays: Int! - SUNDAY + """ + The start date of the most recent active streak, up until the current date. + """ + streakStart: Date } type Giveaway { @@ -401,6 +391,18 @@ type Giveaway { users: [User] } +type WorkoutGoalHistory { + id: ID! + + userId: Int! + + workoutGoal: Int! + + effectiveAt: DateTime! + + user: User +} + type Friendship { id: ID! @@ -419,6 +421,13 @@ type Friendship { friend: User } +""" +The `Date` scalar type represents a Date +value as specified by +[iso8601](https://en.wikipedia.org/wiki/ISO_8601). +""" +scalar Date + type Workout { id: ID! @@ -427,12 +436,9 @@ type Workout { userId: Int! facilityId: Int! -} -""" -JSON String -""" -scalar JSONString + gymName: String! +} type HourlyAverageCapacity { id: ID! @@ -448,6 +454,22 @@ type HourlyAverageCapacity { history: [Float]! } +enum DayOfWeekGraphQLEnum { + MONDAY + + TUESDAY + + WEDNESDAY + + THURSDAY + + FRIDAY + + SATURDAY + + SUNDAY +} + type CapacityReminder { id: ID! @@ -518,7 +540,7 @@ type Mutation { """ Set a user's workout goals. """ - setWorkoutGoals("The ID of the user." userId: Int!, "The new workout goal for the user in terms of days of the week." workoutGoal: [String]!): User + setWorkoutGoals("The ID of the user." userId: Int!, "The new workout goal for the user in terms of number of days per week." workoutGoal: Int!): User """ Log a user's workout. @@ -545,6 +567,11 @@ type Mutation { """ createReport(createdAt: DateTime!, description: String!, gymId: Int!, issue: String!): CreateReport + """ + Deletes a report by ID. + """ + deleteReport(reportId: Int!): Report + """ Deletes a user by ID. """ From ab5f58c25c15171d25797fd676848de9c79cc820 Mon Sep 17 00:00:00 2001 From: Melissa Velasquez Date: Sun, 8 Mar 2026 00:55:15 -0500 Subject: [PATCH 2/6] Updated UI to match designs --- .../profile/ProfileHeaderSection.kt | 60 +++++-- .../profile/workouts/HistorySection.kt | 113 ++++++++++++-- .../profile/workouts/WeeklyProgressTracker.kt | 16 +- .../profile/workouts/WorkoutProgressArc.kt | 86 +++++++--- .../profile/workouts/WorkoutSection.kt | 41 +++++ .../ui/screens/profile/ProfileScreen.kt | 147 ++++++++---------- 6 files changed, 317 insertions(+), 146 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt index 2a156f57..2fa3f8fd 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt @@ -1,14 +1,18 @@ package com.cornellappdev.uplift.ui.components.profile +import android.R.attr.fontFamily +import android.R.attr.fontWeight import android.net.Uri import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Surface @@ -19,6 +23,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -34,7 +39,7 @@ fun ProfileHeaderSection( name: String, gymDays: Int, streaks: Int, - badges: Int, + netID: String, profilePictureUri: Uri?, onPhotoSelected: (Uri) -> Unit ){ @@ -48,7 +53,7 @@ fun ProfileHeaderSection( onPhotoSelected = onPhotoSelected, screenType = ScreenType.PROFILE ) - ProfileHeaderInfoDisplay(name, gymDays, streaks, badges) + ProfileHeaderInfoDisplay(name, gymDays, streaks, netID, modifier = Modifier.weight(1f)) } } @@ -57,34 +62,55 @@ private fun ProfileHeaderInfoDisplay( name: String, gymDays: Int, streaks: Int, - badges: Int + netID: String, + modifier: Modifier = Modifier ) { Column( + modifier = modifier, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( - text = name, - fontFamily = montserratFamily, - fontSize = 24.sp, - fontWeight = FontWeight.Bold - ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = name, + modifier = Modifier.weight(1f, fill = false), + fontFamily = montserratFamily, + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (netID.isNotBlank()){ + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "($netID)", + fontFamily = montserratFamily, + fontSize = 12.sp, + fontWeight = FontWeight.Light, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween ) { ProfileHeaderInfo( label = "Gym Days", amount = gymDays ) + Spacer(modifier = Modifier.width(36.dp)) ProfileHeaderInfo( label = "Streaks", amount = streaks ) - ProfileHeaderInfo( - label = "Badges", - amount = badges - ) +// ProfileHeaderInfo( +// label = "Badges", +// amount = badges +// ) } } } @@ -115,11 +141,11 @@ private fun ProfileHeaderInfo(label: String, amount: Int) { @Composable private fun ProfileHeaderSectionPreview() { ProfileHeaderSection( - name = "John Doe", + name = "Melissa Velasquez", gymDays = 100, streaks = 15, - badges = 3, profilePictureUri = null, - onPhotoSelected = {} + onPhotoSelected = {}, + netID = "mv477" ) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt index 2a614348..e97b7d12 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt @@ -1,42 +1,74 @@ package com.cornellappdev.uplift.ui.components.profile.workouts +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.focusModifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.cornellappdev.uplift.R import com.cornellappdev.uplift.ui.components.profile.SectionTitleText import com.cornellappdev.uplift.util.GRAY01 import com.cornellappdev.uplift.util.montserratFamily +import com.cornellappdev.uplift.util.timeAgoString +import java.util.Calendar data class HistoryItem( val gymName: String, val time: String, - val dayOfWeek: String, val date: String, + val timestamp: Long ) @Composable fun HistorySection( historyItems: List, - onClick : () -> Unit + onClick : () -> Unit, + modifier: Modifier = Modifier ) { Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center ) { - SectionTitleText("My History", onClick) - HistoryList(historyItems) + SectionTitleText("My Workout History", onClick) + Spacer(modifier = Modifier.height(12.dp)) + if (historyItems.isNotEmpty()) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + HistoryList(historyItems) + } + } else { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + EmptyHistorySection() + } + } } @@ -60,26 +92,72 @@ private fun HistoryItemRow( ) { val gymName = historyItem.gymName val time = historyItem.time - val dayOfWeek = historyItem.dayOfWeek val date = historyItem.date + val calendar = Calendar.getInstance().apply { + timeInMillis = historyItem.timestamp + } + val ago = calendar.timeAgoString() + Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp), horizontalArrangement = Arrangement.SpaceBetween ) { + Column(){ + Text( + text = gymName, + fontFamily = montserratFamily, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + color = Color.Black + ) + Text( + text = "$date · $time", + fontFamily = montserratFamily, + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = Color.Gray + ) + } Text( - text = gymName, + text = ago, fontFamily = montserratFamily, - fontSize = 14.sp, + fontSize = 12.sp, fontWeight = FontWeight.Medium, color = Color.Black ) + } +} + +@Composable +private fun EmptyHistorySection(){ + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.ic_bag), + contentDescription = null, + modifier = Modifier + .width(64.99967.dp) + .height(50.8181.dp) + + ) + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = "No workouts yet.", + fontFamily = montserratFamily, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) Text( - text = "$time · $dayOfWeek $date", + text = "Head to a gym and check in!", fontFamily = montserratFamily, - fontSize = 12.sp, - fontWeight = FontWeight.Light, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, color = Color.Black ) } @@ -88,12 +166,13 @@ private fun HistoryItemRow( @Preview(showBackground = true) @Composable private fun HistorySectionPreview() { + val now = System.currentTimeMillis() val historyItems = listOf( - HistoryItem("Morrison", "11:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Noyes", "1:00 PM","Fri", "March 29, 2024"), - HistoryItem("Teagle Up", "2:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Teagle Down", "12:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Helen Newman", "10:00 AM", "Fri", "March 29, 2024"), + HistoryItem("Morrison", "11:00 PM", "March 29, 2024", now - (1 * 24 * 60 * 60 * 1000) ), + HistoryItem("Noyes", "1:00 PM", "March 29, 2024", now - (3 * 24 * 60 * 60 * 1000)), + HistoryItem("Teagle Up", "2:00 PM", "March 29, 2024", now - (7 * 24 * 60 * 60 * 1000)), + HistoryItem("Teagle Down", "12:00 PM", "March 29, 2024", now - (15 * 24 * 60 * 60 * 1000)), + HistoryItem("Helen Newman", "10:00 AM", "March 29, 2024", now), ) Column( modifier = Modifier diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WeeklyProgressTracker.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WeeklyProgressTracker.kt index 28e10813..5cdf7338 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WeeklyProgressTracker.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WeeklyProgressTracker.kt @@ -48,15 +48,23 @@ fun WeeklyProgressTracker( completedDays: List ) { val daysOfWeek = listOf("M", "T", "W", "Th", "F", "Sa", "Su") - val paddedCompletedDays = if (completedDays.size < daysOfWeek.size) { - completedDays + List(daysOfWeek.size - completedDays.size) { false } - } else { - completedDays + + if (daysOfMonth.size < daysOfWeek.size) { + return } + + val paddedCompletedDays = + if (completedDays.size < daysOfWeek.size) { + completedDays + List(daysOfWeek.size - completedDays.size) { false } + } else { + completedDays + } + val lastCompletedIndex = paddedCompletedDays.indexOfLast { it } Box(modifier = Modifier.fillMaxWidth()) { ConnectingLines(daysOfWeek, lastCompletedIndex) + DayProgressCirclesRow( dayProgressList = daysOfWeek.mapIndexed { index, dayOfWeek -> DayProgress( diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutProgressArc.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutProgressArc.kt index be32012f..a870c287 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutProgressArc.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutProgressArc.kt @@ -52,9 +52,16 @@ fun WorkoutProgressArc( workoutsCompleted: Int, workoutGoal: Int, ) { - // Calculate progress percentage - val progress = (workoutsCompleted.toFloat() / workoutGoal.toFloat()).coerceIn(0f, 1f) + val isZero = workoutsCompleted <= 0 || workoutGoal <= 0 + val isComplete = workoutGoal > 0 && workoutsCompleted >= workoutGoal + // Calculate progress percentage + val progress = when { + workoutGoal <= 0 -> 0f + workoutsCompleted <= 0 -> 0f + else -> (workoutsCompleted.toFloat() / workoutGoal.toFloat()) + .coerceIn(0f, 1f) + } // Setup animation val animatedProgress = remember { Animatable(0f) } @@ -74,16 +81,16 @@ fun WorkoutProgressArc( .height(132.dp) ) { // Draw the progress arc - ProgressArc(animatedProgress, workoutsCompleted, workoutGoal) - WorkoutFractionTextSection(workoutsCompleted, workoutGoal) + ProgressArc(animatedProgress, isZero, isComplete) + WorkoutFractionTextSection(workoutsCompleted, workoutGoal, isComplete) } } @Composable private fun ProgressArc( animatedProgress: Animatable, - workoutsCompleted: Int, - workoutGoal: Int + isZero: Boolean, + isComplete: Boolean ) { val startAngle = 180f; val maxSweepAngle = 180f; @@ -112,16 +119,29 @@ private fun ProgressArc( // Progress arc val progressAngle = maxSweepAngle * animatedProgress.value - drawProgressArc( - workoutsCompleted, - workoutGoal, - gradientBrush, - startAngle, - progressAngle, - topLeft, - arcSize, - strokeWidth - ) + if (progressAngle > 0f) { + if (isComplete) { + drawArc( + brush = gradientBrush, + startAngle = startAngle, + sweepAngle = progressAngle, + useCenter = false, + topLeft = topLeft, + size = arcSize, + style = Stroke(width = strokeWidth, cap = StrokeCap.Round) + ) + } else { + drawArc( + color = PRIMARY_YELLOW, + startAngle = startAngle, + sweepAngle = progressAngle, + useCenter = false, + topLeft = topLeft, + size = arcSize, + style = Stroke(width = strokeWidth, cap = StrokeCap.Round) + ) + } + } // Progress arc circle val angle = Math.toRadians((startAngle + progressAngle).toDouble()) @@ -134,7 +154,29 @@ private fun ProgressArc( val y = arcCenterY + (radius * sin(angle)).toFloat() // Outer circle - drawArcSliderOuterCircle(workoutsCompleted, workoutGoal, gradientBrush, dotRadius, x, y) + when { + isComplete -> { + drawCircle( + brush = gradientBrush, + radius = dotRadius, + center = Offset(x, y) + ) + } + isZero -> { + drawCircle( + color = GRAY03, + radius = dotRadius, + center = Offset(x, y) + ) + } + else -> { + drawCircle( + color = PRIMARY_YELLOW, + radius = dotRadius, + center = Offset(x, y) + ) + } + } // Inner circle drawCircle( @@ -215,7 +257,7 @@ private fun DrawScope.drawArcSliderOuterCircle( } @Composable -private fun WorkoutFractionTextSection(workoutsCompleted: Int, workoutGoal: Int) { +private fun WorkoutFractionTextSection(workoutsCompleted: Int, workoutGoal: Int, isComplete: Boolean) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), @@ -225,7 +267,7 @@ private fun WorkoutFractionTextSection(workoutsCompleted: Int, workoutGoal: Int) verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - WorkoutsCompletedText(workoutsCompleted, workoutGoal) + WorkoutsCompletedText(workoutsCompleted, isComplete) Text( text = "/ $workoutGoal", @@ -237,7 +279,7 @@ private fun WorkoutFractionTextSection(workoutsCompleted: Int, workoutGoal: Int) ) } Text( - text = "Workouts this week", + text = "Days this week", fontSize = 14.sp, color = GRAY04, fontFamily = montserratFamily @@ -246,8 +288,8 @@ private fun WorkoutFractionTextSection(workoutsCompleted: Int, workoutGoal: Int) } @Composable -private fun WorkoutsCompletedText(workoutsCompleted: Int, workoutGoal: Int) { - if (workoutsCompleted == workoutGoal) { +private fun WorkoutsCompletedText(workoutsCompleted: Int, isComplete: Boolean) { + if (isComplete) { Text( text = "$workoutsCompleted", fontSize = 64.sp, diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt new file mode 100644 index 00000000..43854b80 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt @@ -0,0 +1,41 @@ +package com.cornellappdev.uplift.ui.components.profile.workouts + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +private fun WorkoutsSectionContent( + workoutsCompleted: Int, + workoutGoal: Int, + daysOfMonth: List, + completedDays: List, + reminderItems: List, + historyItems: List, + navigateToGoalsSection: () -> Unit, + navigateToRemindersSection: () -> Unit, + navigateToHistorySection: () -> Unit +) { + Column( + modifier = Modifier.fillMaxSize() + ) { + GoalsSection( + workoutsCompleted = workoutsCompleted, + workoutGoal = workoutGoal, + daysOfMonth = daysOfMonth, + completedDays = completedDays, + onClick = navigateToGoalsSection, + ) + Spacer(modifier = Modifier.height(24.dp)) + + HistorySection( + historyItems = historyItems, + onClick = navigateToHistorySection, + modifier = Modifier.weight(1f) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt b/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt index 695c299c..86ec4011 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt @@ -1,10 +1,15 @@ package com.cornellappdev.uplift.ui.screens.profile +import android.R.attr.name import android.annotation.SuppressLint +import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsEndWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -15,6 +20,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember @@ -27,7 +34,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.viewmodel.compose.viewModel import com.cornellappdev.uplift.R +import com.cornellappdev.uplift.data.repositories.ProfileRepository +import com.cornellappdev.uplift.data.repositories.UserInfoRepository import com.cornellappdev.uplift.ui.components.general.UpliftTabRow import com.cornellappdev.uplift.ui.components.profile.workouts.GoalsSection import com.cornellappdev.uplift.ui.components.profile.workouts.HistoryItem @@ -35,96 +46,63 @@ import com.cornellappdev.uplift.ui.components.profile.workouts.HistorySection import com.cornellappdev.uplift.ui.components.profile.workouts.MyRemindersSection import com.cornellappdev.uplift.ui.components.profile.ProfileHeaderSection import com.cornellappdev.uplift.ui.components.profile.workouts.ReminderItem +import com.cornellappdev.uplift.ui.viewmodels.profile.ProfileViewModel import com.cornellappdev.uplift.util.GRAY01 import com.cornellappdev.uplift.util.montserratFamily @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ProfileScreen() { - /* TODO: Replace with call to viewmodel */ - val name = "John Doe" - /* TODO: Replace with call to viewmodel */ - val gymDays = 132 - /* TODO: Replace with call to viewmodel */ - val streaks = 14 - /* TODO: Replace with call to viewmodel */ - val badges = 6 - /* TODO: Replace with call to viewmodel */ - val profilePicture = null - /* TODO: Replace with call to viewmodel */ - val workoutsCompleted = 3 - /* TODO: Replace with call to viewmodel */ - val workoutGoal = 5 - /* TODO: Replace with call to viewmodel */ - val daysOfMonth = (25..31).toList() - /* TODO: Replace with call to viewmodel */ - val completedDays = listOf(false, true, true, false, true, false, false) - /* TODO: Replace with call to viewmodel */ - val reminderItems = listOf( - ReminderItem("Mon", "8:00 AM", "9:00 AM", true), - ReminderItem("Tue", "8:00 AM", "12:00 PM", false), - ReminderItem("Wed", "8:00 AM", "9:00 AM", true), - ReminderItem("Thu", "8:00 AM", "9:00 AM", false), - ReminderItem("Fri", "11:30 AM", "12:00 PM", true), - ) - /* TODO: Replace with call to viewmodel */ - val historyItems = listOf( - HistoryItem("Morrison", "11:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Noyes", "1:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Teagle Up", "2:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Teagle Down", "12:00 PM", "Fri", "March 29, 2024"), - HistoryItem("Helen Newman", "10:00 AM", "Fri", "March 29, 2024"), - ) +fun ProfileScreen( + viewModel: ProfileViewModel = hiltViewModel(), + toSettings:() -> Unit, + toGoals:() -> Unit, + toHistory:() -> Unit +) { +// var tabIndex by remember { mutableIntStateOf(0) } +// val tabs = listOf("WORKOUTS", "ACHIEVEMENTS") - var tabIndex by remember { mutableIntStateOf(0) } - val tabs = listOf("WORKOUTS", "ACHIEVEMENTS") + val uiState by viewModel.uiStateFlow.collectAsState() - val scrollState = rememberScrollState() + LaunchedEffect(Unit) { + viewModel.reload() + } Scaffold( containerColor = Color.White, topBar = { - /* TODO: Replace {} with viewmodel nav call */ - ProfileScreenTopBar(navigateToSettings = {}) + ProfileScreenTopBar(navigateToSettings = toSettings) } ) { innerPadding -> Column( - verticalArrangement = Arrangement.spacedBy(24.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier .fillMaxSize() - .verticalScroll(scrollState) .padding( top = innerPadding.calculateTopPadding() + 24.dp, start = 16.dp, end = 16.dp, ) ) { - /* TODO: Replace {} with viewmodel function call */ ProfileHeaderSection( - name = name, - gymDays = gymDays, - streaks = streaks, - badges = badges, - profilePictureUri = profilePicture, - onPhotoSelected = {} + name = uiState.name, + gymDays = uiState.totalGymDays, + streaks = uiState.activeStreak, + profilePictureUri = uiState.profileImage?.let { Uri.parse(it) }, + onPhotoSelected = {}, + netID = uiState.netId + ) + WorkoutsSectionContent( + workoutsCompleted = uiState.workoutsCompleted, + workoutGoal = uiState.workoutGoal, + daysOfMonth = uiState.daysOfMonth, + completedDays = uiState.completedDays, + reminderItems= emptyList(), //implement + historyItems = uiState.historyItems, + navigateToGoalsSection = toGoals, + navigateToRemindersSection = { /* TODO: Replace {} with viewmodel nav call */ }, + navigateToHistorySection = toHistory ) - UpliftTabRow(tabIndex, tabs, onTabChange = { tabIndex = it }) - when (tabIndex) { - 0 -> WorkoutsSectionContent( - workoutsCompleted, - workoutGoal, - daysOfMonth, - completedDays, - reminderItems, - historyItems, - navigateToGoalsSection = { /* TODO: Replace {} with viewmodel nav call */ }, - navigateToRemindersSection = { /* TODO: Replace {} with viewmodel nav call */ }, - navigateToHistorySection = { /* TODO: Replace {} with viewmodel nav call */ } - ) - - 1 -> AchievementsSectionContent() - } } } @@ -142,21 +120,24 @@ private fun WorkoutsSectionContent( navigateToRemindersSection: () -> Unit, navigateToHistorySection: () -> Unit ) { - GoalsSection( - workoutsCompleted = workoutsCompleted, - workoutGoal = workoutGoal, - daysOfMonth = daysOfMonth, - completedDays = completedDays, - onClick = navigateToGoalsSection, - ) - MyRemindersSection( - reminderItems, - onClickHeader = navigateToRemindersSection, - ) - HistorySection( - historyItems = historyItems, - onClick = navigateToHistorySection, - ) + Column( + modifier = Modifier.fillMaxSize() + ) { + GoalsSection( + workoutsCompleted = workoutsCompleted, + workoutGoal = workoutGoal, + daysOfMonth = daysOfMonth, + completedDays = completedDays, + onClick = navigateToGoalsSection, + ) + Spacer(modifier = Modifier.height(24.dp)) + + HistorySection( + historyItems = historyItems, + onClick = navigateToHistorySection, + modifier = Modifier.weight(1f) + ) + } } //TODO: Implement AchievementsSection @@ -196,9 +177,3 @@ private fun ProfileScreenTopBar( } ) } - -@Preview(showBackground = true) -@Composable -private fun ProfileScreenPreview() { - ProfileScreen() -} \ No newline at end of file From ab3224189f11a1018b0787c632ea1a0ecd482a4e Mon Sep 17 00:00:00 2001 From: Melissa Velasquez Date: Sun, 8 Mar 2026 00:56:17 -0500 Subject: [PATCH 3/6] Implemented ProfileRepo and updated User model and repo --- .../uplift/data/models/UserInfo.kt | 8 ++ .../data/repositories/ProfileRepository.kt | 129 ++++++++++++++++++ .../data/repositories/UserInfoRepository.kt | 38 +++++- 3 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt diff --git a/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt b/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt index d10aa9e1..ca678b75 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt @@ -1,10 +1,18 @@ package com.cornellappdev.uplift.data.models +import com.cornellappdev.uplift.type.DateTime import kotlinx.serialization.Serializable + @Serializable data class UserInfo( val id: String, val email: String, val name: String, val netId: String, + val encodedImage: String?, + val activeStreak: Int?, + val maxStreak: Int?, + val streakStart: String?, + val workoutGoal: Int?, + val totalGymDays: Int ) \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt b/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt new file mode 100644 index 00000000..1471ed31 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt @@ -0,0 +1,129 @@ +package com.cornellappdev.uplift.data.repositories + +import android.util.Log +import com.apollographql.apollo.ApolloClient +import com.cornellappdev.uplift.GetUserByNetIdQuery +import com.cornellappdev.uplift.GetWeeklyWorkoutDaysQuery +import com.cornellappdev.uplift.GetWorkoutsByIdQuery +import com.cornellappdev.uplift.SetWorkoutGoalsMutation +import java.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +data class ProfileData( + val name: String, + val netId: String, + val encodedImage: String?, + val totalGymDays: Int, + val activeStreak: Int, + val maxStreak: Int, + val streakStart: String?, + val workoutGoal: Int, + val workouts: List, + val weeklyWorkoutDays: List +) + +data class WorkoutDomain( + val gymName: String, + val timestamp: Long +) + +@Singleton +class ProfileRepository @Inject constructor( + private val userInfoRepository: UserInfoRepository, + private val apolloClient: ApolloClient +) { + suspend fun getProfile(): Result { + return try{ + val netId = userInfoRepository.getNetIdFromDataStore() + ?: return Result.failure(Exception("NetId missing")) + + val userResponse = apolloClient.query( + GetUserByNetIdQuery(netId) + ).execute() + + if (userResponse.hasErrors()) { + Log.e("ProfileRepo", "User query errors: ${userResponse.errors}") + return Result.failure(IllegalStateException("User query failed")) + } + + val user = userResponse.data?.getUserByNetId?.firstOrNull()?.userFields + ?: return Result.failure(IllegalStateException("User not found")) + + val userId = user.id.toIntOrNull() + ?: return Result.failure(IllegalStateException("Invalid user ID: ${user.id}")) + + val workoutResponse = apolloClient + .query(GetWorkoutsByIdQuery(userId)) + .execute() + + if (workoutResponse.hasErrors()) { + Log.e("ProfileRepo", "Workout query errors: ${workoutResponse.errors}") + } + + val workouts = if (workoutResponse.hasErrors()) { + emptyList() + } else { + workoutResponse.data?.getWorkoutsById?.filterNotNull() ?: emptyList() + } + + val workoutDomain = workouts.map { + WorkoutDomain( + gymName = it.workoutFields.gymName, + timestamp = Instant.parse(it.workoutFields.workoutTime.toString()).toEpochMilli() + ) + } + + val weeklyResponse = apolloClient.query(GetWeeklyWorkoutDaysQuery(userId)).execute() + if (weeklyResponse.hasErrors()) { + Log.e("ProfileRepo", "Weekly query errors=${weeklyResponse.errors}") + } + + val weeklyDays = if (weeklyResponse.hasErrors()) { + emptyList() + } else { + weeklyResponse.data?.getWeeklyWorkoutDays?.filterNotNull() ?: emptyList() + } + + Result.success( + ProfileData( + name = user.name, + netId = user.netId, + encodedImage = user.encodedImage, + totalGymDays = user.totalGymDays, + activeStreak = user.activeStreak ?: 0, + maxStreak = user.maxStreak ?: 0, + streakStart = user.streakStart?.toString(), + workoutGoal = user.workoutGoal ?: 0, + workouts = workoutDomain, + weeklyWorkoutDays = weeklyDays + ) + ) + } catch (e: Exception) { + Log.e("ProfileRepo", "Failed to load profile", e) + Result.failure(e) + } + } + + suspend fun setWorkoutGoal(userId: Int, goal: Int): Result { + return try { + val response = apolloClient + .mutation( + SetWorkoutGoalsMutation( + id = userId, + workoutGoal = goal + ) + ) + .execute() + + if (response.hasErrors()) { + Result.failure(Exception("Goal update failed")) + } else { + Result.success(Unit) + } + + } catch (e: Exception) { + Result.failure(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt b/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt index c0166b90..a5ec7907 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt @@ -12,6 +12,7 @@ import com.cornellappdev.uplift.GetUserByNetIdQuery import kotlinx.coroutines.flow.map; import kotlinx.coroutines.flow.firstOrNull import com.cornellappdev.uplift.data.models.UserInfo +import com.cornellappdev.uplift.type.DayOfWeekEnum import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.GoogleAuthProvider @@ -33,10 +34,13 @@ class UserInfoRepository @Inject constructor( netId = netId, ) ).execute() - storeId(response.data?.createUser?.userFields?.let { storeId(it.id) }.toString()) - storeNetId(netId) - storeUsername(name) - storeEmail(email) + + val createdUser = response.data?.createUser?.userFields ?: return false + + storeId(createdUser.id) + storeNetId(createdUser.netId) + storeUsername(createdUser.name) + storeEmail(createdUser.email ?: email) storeSkip(false) Log.d("UserInfoRepositoryImpl", "User created successfully" + response.data) return true @@ -46,6 +50,24 @@ class UserInfoRepository @Inject constructor( } } + suspend fun syncUserToDataStore(netId: String): Boolean { + return try { + val user = getUserByNetId(netId) ?: return false + + storeId(user.id) + storeNetId(user.netId) + storeUsername(user.name) + storeEmail(user.email) + storeSkip(false) + + Log.d("UserInfoRepositoryImpl", "Synced existing user to DataStore: ${user.id}") + true + } catch (e: Exception) { + Log.e("UserInfoRepositoryImpl", "Error syncing user to DataStore", e) + false + } + } + suspend fun getUserByNetId(netId: String): UserInfo? { try { val response = apolloClient.query( @@ -53,12 +75,18 @@ class UserInfoRepository @Inject constructor( netId = netId ) ).executeV3() - val user = response.data?.getUserByNetId?.get(0)?.userFields ?: return null + val user = response.data?.getUserByNetId?.firstOrNull()?.userFields ?: return null return UserInfo( id = user.id, name = user.name, email = user.email ?: "", netId = user.netId, + encodedImage = user.encodedImage, + activeStreak = user.activeStreak, + maxStreak = user.maxStreak, + workoutGoal = user.workoutGoal, + streakStart = user.streakStart?.toString(), + totalGymDays = user.totalGymDays ) } catch (e: Exception) { Log.e("UserInfoRepositoryImpl", "Error getting user by netId: $e") From ac5c5cfc35d94450784c0d89b4d5f5087f9ea0c8 Mon Sep 17 00:00:00 2001 From: Melissa Velasquez Date: Sun, 8 Mar 2026 00:58:17 -0500 Subject: [PATCH 4/6] Implemented Profile VM, updates Login VM and network # Conflicts: # app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt --- .../uplift/ui/MainNavigationWrapper.kt | 14 +- .../viewmodels/onboarding/LoginViewModel.kt | 14 +- .../ui/viewmodels/profile/ProfileViewModel.kt | 135 ++++++++++++++++++ .../cornellappdev/uplift/util/Functions.kt | 35 +++++ app/src/main/res/drawable/ic_bag.png | Bin 0 -> 2564 bytes 5 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt create mode 100644 app/src/main/res/drawable/ic_bag.png diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt b/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt index ab6ac345..8da9e727 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt @@ -55,6 +55,7 @@ import com.cornellappdev.uplift.ui.viewmodels.profile.CheckInUiState import com.cornellappdev.uplift.ui.viewmodels.profile.CheckInViewModel import com.cornellappdev.uplift.util.ONBOARDING_FLAG import com.cornellappdev.uplift.ui.viewmodels.profile.ConfettiViewModel +import com.cornellappdev.uplift.ui.viewmodels.profile.ProfileViewModel import com.cornellappdev.uplift.util.CHECK_IN_FLAG import com.cornellappdev.uplift.util.PRIMARY_BLACK import com.cornellappdev.uplift.util.PRIMARY_YELLOW @@ -77,14 +78,23 @@ fun MainNavigationWrapper( classDetailViewModel: ClassDetailViewModel = hiltViewModel(), rootNavigationViewModel: RootNavigationViewModel = hiltViewModel(), - ) { +) { + val confettiViewModel: ConfettiViewModel = hiltViewModel() + val checkInViewModel: CheckInViewModel = hiltViewModel() val rootNavigationUiState = rootNavigationViewModel.collectUiStateValue() val startDestination = rootNavigationUiState.startDestination val navController = rememberNavController() val systemUiController: SystemUiController = rememberSystemUiController() + val checkInUiState = checkInViewModel.collectUiStateValue() + val confettiUiState = confettiViewModel.collectUiStateValue() + + val profileViewModel: ProfileViewModel = hiltViewModel() + + + val yourShimmerTheme = defaultShimmerTheme.copy( shaderColors = listOf( Color.Unspecified.copy(alpha = 1f), @@ -241,7 +251,7 @@ fun MainNavigationWrapper( CapacityReminderScreen() } composable { - ProfileScreen() + ProfileScreen(profileViewModel, {}, {}, {}) } composable { MainReminderScreen() diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/onboarding/LoginViewModel.kt b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/onboarding/LoginViewModel.kt index aa0b3dcf..bcb7babe 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/onboarding/LoginViewModel.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/onboarding/LoginViewModel.kt @@ -42,14 +42,20 @@ class LoginViewModel @Inject constructor( return@launch } when { - userInfoRepository.hasUser(netId) -> rootNavigationRepository.navigate( - UpliftRootRoute.Home - ) + userInfoRepository.hasUser(netId) -> { + val synced = userInfoRepository.syncUserToDataStore(netId) + if (synced) { + rootNavigationRepository.navigate(UpliftRootRoute.Home) + } else { + Log.e("Error", "Failed to sync existing user") + userInfoRepository.signOut() + } + } userInfoRepository.hasFirebaseUser() -> rootNavigationRepository.navigate( UpliftRootRoute.ProfileCreation ) - //TODO: Handle error + else -> { Log.e("Error", "Unexpected credential") userInfoRepository.signOut() diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt new file mode 100644 index 00000000..8d24118d --- /dev/null +++ b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt @@ -0,0 +1,135 @@ +package com.cornellappdev.uplift.ui.viewmodels.profile + +import android.util.Log +import androidx.lifecycle.viewModelScope +import com.cornellappdev.uplift.data.repositories.ProfileRepository +import com.cornellappdev.uplift.data.repositories.UpliftApiRepository +import com.cornellappdev.uplift.data.repositories.UserInfoRepository +import com.cornellappdev.uplift.ui.components.profile.workouts.HistoryItem +import com.cornellappdev.uplift.ui.viewmodels.UpliftViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import okhttp3.internal.format +import java.time.DayOfWeek +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.Locale +import javax.inject.Inject + +data class ProfileUiState( + val loading: Boolean = false, + val error: Boolean = false, + val name: String = "", + val netId: String = "", + val profileImage: String? = null, + val totalGymDays: Int = 0, + val activeStreak: Int = 0, + val maxStreak: Int = 0, + val streakStart: String? = null, + val workoutGoal: Int = 0, + val historyItems: List = emptyList(), + val daysOfMonth: List = emptyList(), + val completedDays: List = emptyList(), + val workoutsCompleted: Int = 0 +) + +@HiltViewModel +class ProfileViewModel @Inject constructor( + private val profileRepository: ProfileRepository, + private val userInfoRepository: UserInfoRepository, +) : UpliftViewModel(ProfileUiState()) { + + private var loadingJob: Job? = null + + fun reload() { + if (loadingJob?.isActive == true) return + loadingJob = loadProfile() + + } + + + private fun loadProfile(): Job = viewModelScope.launch { + applyMutation { copy(loading = true, error = false) } + + val result = profileRepository.getProfile() + + if (result.isSuccess) { + val profile = result.getOrNull()!! + + val historyItems = profile.workouts.map { + HistoryItem( + gymName = it.gymName, + time = formatTime.format( + Instant.ofEpochMilli(it.timestamp) + ), + date = formatDate.format( + Instant.ofEpochMilli(it.timestamp) + ), + timestamp = it.timestamp + ) + } + + val now = LocalDate.now() + val startOfWeek = now.with(DayOfWeek.MONDAY) + + val weekDates = (0..6).map { + startOfWeek.plusDays(it.toLong()) + } + + val daysOfMonth = weekDates.map { it.dayOfMonth } + + val completedDays = weekDates.map { date -> + profile.weeklyWorkoutDays.contains(date.toString()) + } + + val workoutsCompleted = profile.weeklyWorkoutDays.size + + applyMutation { + copy( + loading = false, + name = profile.name, + netId = profile.netId, + profileImage = profile.encodedImage, + totalGymDays = profile.totalGymDays, + activeStreak = profile.activeStreak, + maxStreak = profile.maxStreak, + streakStart = profile.streakStart, + workoutGoal = profile.workoutGoal, + historyItems = historyItems, + daysOfMonth = daysOfMonth, + completedDays = completedDays, + workoutsCompleted = workoutsCompleted + ) + } + } else { + Log.e("profile VM", "Failed to load profile", result.exceptionOrNull()) + applyMutation { copy(loading = false, error = true) } + } + + } + + + fun updateWorkoutGoal(goal: Int) = viewModelScope.launch { + val userId = userInfoRepository.getUserIdFromDataStore()?.toIntOrNull() ?: return@launch + val result = profileRepository.setWorkoutGoal(userId, goal) + + if (result.isSuccess) { + reload() + } else { + Log.e("profile VM", "Failed to update workout goal", result.exceptionOrNull()) + } + } + + private val formatTime = DateTimeFormatter + .ofPattern("h:mm a") + .withLocale(Locale.US) + .withZone(ZoneId.systemDefault()) + + private val formatDate = DateTimeFormatter + .ofPattern("MMMM d, yyyy • h:mm a") + .withLocale(Locale.US) + .withZone(ZoneId.systemDefault()) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/util/Functions.kt b/app/src/main/java/com/cornellappdev/uplift/util/Functions.kt index d3cf75ab..0988bc52 100644 --- a/app/src/main/java/com/cornellappdev/uplift/util/Functions.kt +++ b/app/src/main/java/com/cornellappdev/uplift/util/Functions.kt @@ -183,3 +183,38 @@ val startTimeComparator = { class1: UpliftClass, class2: UpliftClass -> class1.time.end.compareTo(class2.time.end) } } + +/** + * Returns a relative time string such as: + * "1 day ago", "2 weeks ago", "1 month ago", "1 year ago" + */ +fun Calendar.timeAgoString(): String { + val now = Calendar.getInstance() + + val diffMillis = now.timeInMillis - this.timeInMillis + if (diffMillis < 0) return "Today" + + val diffDays = diffMillis / (1000 * 60 * 60 * 24) + + val diffWeeks = diffDays / 7 + val diffMonths = diffDays / 30 + val diffYears = diffDays / 365 + + return when { + diffDays < 1 -> "Today" + + diffDays == 1L -> "1 day ago" + diffDays in 2..6 -> "$diffDays days ago" + + diffWeeks == 1L -> "1 week ago" + diffWeeks in 2..4 -> "$diffWeeks weeks ago" + + diffMonths == 1L -> "1 month ago" + diffMonths in 2..11 -> "$diffMonths months ago" + + diffYears == 1L -> "1 year ago" + diffYears > 1L -> "$diffYears years ago" + + else -> "Today" + } +} diff --git a/app/src/main/res/drawable/ic_bag.png b/app/src/main/res/drawable/ic_bag.png new file mode 100644 index 0000000000000000000000000000000000000000..41b1efe9c23f3c93f1ef8a6a07c9a6c3d9de8921 GIT binary patch literal 2564 zcmV+f3j6hmP)?KiCDJ+(qC=NHH8 z2?W++D_pcz{P+%R-Fy!$xj_ibo1p*GJm|af2=bw{%5t#L?RL*VhLAB5kbrt5pmkr( zBXcf~g$|$Vo&1A4*FaZt|d5{4Zt^|4z zlVEE3!yEsCd}}b=-v8-3_}!bE;PaupksCMHSY7BNR1gtJP|n2dZ0qpwa0eI+!sOXUp|2BXcO_s@@@ z^UEGV0*-gOJ4etv|MBbxHjEO>xAWAK^H720nHU@cgJG-^7&}JMpa|uy!iV;MzO4{L zA#ehyZAo5w$A_elf6(Gk-2_-HvcyBHfiR4O@^aVf_Vr6*rN-6?=Pwj;6@2`0PpjI- za{^gz?C8voPy^)@moG=-q7yb^s*pS!O;uwZhN zS!nUni__>Rmqg4mUk8I=2niGtNd>MrDj;W=C^g1Z`Z)Ou3Y{uQ4ctkFLZ%^V(47RH z7%^7AFHTNCDj;W==-l33Bu8QIhsat6k%^AdYLpsuC*eV21c_9jI>SWBQcO)mm{tu0 zBh{cQ3CUDIf5}v!f|C?S4HPnqFzYlz4Z4ybfFP|#1u7D$VQ=*`R|Dt_ea(ULf+!VG zL`YQyDmYmT+%&z`qH@##$jwP^7?O%M1~r#m*gbR0S#3(?RM+0_Oj_qJ04ofCUmv z1{d351)wh}Rc=;}hWZdL)m>dpHmX;N-1>inYg)jFD8X@r@+#$`CWR;D}02 zX9?8R)qxWU(Bkx|_u$#>{)o1rFD>A3IH0P!3RVSl45!|`0*!~3nU5q7%`YHDjD z4j&vCgs!gchVJg}hE&N&>Jz9!Y!b}>aF&)x&CPE^^NHpd0tN>M`L{#78_|C4tF_>C zI$^aaD=XtAc~X@YWPV<+H|BV{zoWlC0_VjH-*FV!EU$1nX_l$z`^|@ z6NHv>$(1m-h)V(ykZZ9t(db9ZEgIwFLL_`b;RLa8AR})Dp(dcVIlC(ud1@(p?h|)Cg|~cFhbel_K}^ntR5m_YH}L7KXSL=b4G;G2oiWj$Zi+w>JLJypa&}| zT#9HTC6FygWBc92h<^6!K#d`L2zHTllw1MAe=bz}q~6>lsowbcTHb zsenQnv7QRZA^zpcC7!}k4M7Wv;d8V91&_xQa~_QznNOlWd$eic15(2D?Dn~sz9>zf zo&Fo&pA4i34E21w_=zpxctQkR8x0p{!b+eC(C(^ANC&il+IVOz?!I{PT@4N$oQO%w z2{cvz!kheDBB0QUCN(w6e?L|FB$-7$ZgL{H64VJ$s?P9XM4@%rl%C$nSN*M(QYF14 z5+XsCp_IINJZJW80jVL|9Euieb912JQ7Z%mMWWvie1amT>I)ubSk`_oyuI-U^t_5l z+nfY61*gNY6~67z<_!gp=3`BQiL@PZj51hPlHK_O0z{X;R8Z=yC#b??u>@nb;toR}O-+HbsU zlcw-z&&6JcB2Zw#iqrMf1~6xhCA>|30gFN+ZJRM^;2ttvB~T=xn?11qApEv*G$MS_ z%U3V%se|2ByCL#noT!}Pm~{y)`Z4L!l0d$SX0fh{O8EH!AC&xPxrBG(y$xW_{R!Yq z7gB-N95!wWMGKxJn$2b_jcO($L+wv%c*%{H;NzjAvw*Kxrjxd(18sz$=s;>PnX*gG zYIw{9=sK_BsGUA_nwt#IHc>$&YbweT0Y{G>0V5+-&YE1fzZ5FZ6%<%ubaY%aQ<%tP zeo%`!ei_au-g@&bG@GGtJK246b6rZ)_tij6p9y3VqoZS@`Tr3fRXZn8J;0J@$ylPc z7M4f|ISl^p|P~I{UbkFHrXY$T+p0Y5%KwmRvq++1$!bR!nXLi6> zH$R7TFm*feLJ!^8zbC$7j;jNR?3G^U+Q4=EWbJ|wWQ|lMI(_C{_%5OAqs#F3{sI2p zgt^DvGf*y7zVhM4&X((6{F7H*QpLi2s7w}}*VDY=+Tiu1N?8f0gr|KOoVIVzjX~Fk zmmwWsK?ShRIHSmN>oGM0ZyIWX4xEm aedvEA2;ig&CBxnT0000 Date: Sun, 8 Mar 2026 00:59:04 -0500 Subject: [PATCH 5/6] tested logworkout call for profile page testing, WIP --- .../uplift/data/repositories/CheckInRepository.kt | 10 +++++++++- .../uplift/ui/viewmodels/profile/CheckInViewModel.kt | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt b/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt index d0384d1f..1e8b1656 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt @@ -150,7 +150,14 @@ class CheckInRepository @Inject constructor( * Logs a completed workout to the backend. Returns true if the mutation succeeded, false otherwise. */ suspend fun logWorkoutFromCheckIn(gymId: Int): Boolean { - val userId = userInfoRepository.getUserIdFromDataStore()?.toIntOrNull() ?: return false + val userIdString = userInfoRepository.getUserIdFromDataStore() + val userId = userIdString?.toIntOrNull() + + if (userId == null) { + Log.e("CheckInRepository", "Missing or invalid userId in DataStore: $userIdString") + return false + } + val time = Instant.now().toString() return try { @@ -161,6 +168,7 @@ class CheckInRepository @Inject constructor( val ok = response.data?.logWorkout?.workoutFields != null && !response.hasErrors() if (!ok) { Log.e("CheckInRepository", "LogWorkout errors=${response.errors}") + Log.e("CheckInRepository", "LogWorkout response data=${response.data}") } ok } catch (t: Throwable){ diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/CheckInViewModel.kt b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/CheckInViewModel.kt index 09c05160..0a17dac8 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/CheckInViewModel.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/CheckInViewModel.kt @@ -150,7 +150,12 @@ class CheckInViewModel @Inject constructor( ) } confettiRepository.showConfetti(ConfettiViewModel.ConfettiUiState()) - checkInRepository.logWorkoutFromCheckIn(gymIdInt) + val logged = checkInRepository.logWorkoutFromCheckIn(gymIdInt) + if (logged) { + Log.d(tag, "Workout successfully logged to backend") + } else { + Log.e(tag, "Workout failed to log to backend") + } } catch (e: Exception) { Log.e(tag, "Error checking in", e) } From 361c30791540c90ee6eb44cf59ca3a4a50a7933d Mon Sep 17 00:00:00 2001 From: Melissa Velasquez Date: Mon, 9 Mar 2026 14:56:49 -0400 Subject: [PATCH 6/6] Rebased and Addressed Copilot Comments --- .../uplift/data/models/UserInfo.kt | 1 - .../data/repositories/CheckInRepository.kt | 1 - .../data/repositories/ProfileRepository.kt | 4 +- .../data/repositories/UserInfoRepository.kt | 1 - .../uplift/ui/MainNavigationWrapper.kt | 14 +------ .../profile/ProfileHeaderSection.kt | 19 ++------- .../profile/workouts/HistorySection.kt | 3 -- .../profile/workouts/WorkoutSection.kt | 41 ------------------- .../ui/screens/profile/ProfileScreen.kt | 15 +------ .../ui/viewmodels/profile/ProfileViewModel.kt | 4 +- 10 files changed, 9 insertions(+), 94 deletions(-) delete mode 100644 app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt diff --git a/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt b/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt index ca678b75..c4720923 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt @@ -1,5 +1,4 @@ package com.cornellappdev.uplift.data.models -import com.cornellappdev.uplift.type.DateTime import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt b/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt index 1e8b1656..52e380db 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt @@ -168,7 +168,6 @@ class CheckInRepository @Inject constructor( val ok = response.data?.logWorkout?.workoutFields != null && !response.hasErrors() if (!ok) { Log.e("CheckInRepository", "LogWorkout errors=${response.errors}") - Log.e("CheckInRepository", "LogWorkout response data=${response.data}") } ok } catch (t: Throwable){ diff --git a/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt b/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt index 1471ed31..0e5818cb 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt @@ -91,8 +91,8 @@ class ProfileRepository @Inject constructor( netId = user.netId, encodedImage = user.encodedImage, totalGymDays = user.totalGymDays, - activeStreak = user.activeStreak ?: 0, - maxStreak = user.maxStreak ?: 0, + activeStreak = user.activeStreak, + maxStreak = user.maxStreak, streakStart = user.streakStart?.toString(), workoutGoal = user.workoutGoal ?: 0, workouts = workoutDomain, diff --git a/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt b/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt index a5ec7907..69f02ca0 100644 --- a/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt +++ b/app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt @@ -12,7 +12,6 @@ import com.cornellappdev.uplift.GetUserByNetIdQuery import kotlinx.coroutines.flow.map; import kotlinx.coroutines.flow.firstOrNull import com.cornellappdev.uplift.data.models.UserInfo -import com.cornellappdev.uplift.type.DayOfWeekEnum import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.GoogleAuthProvider diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt b/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt index 8da9e727..5ee8c4c6 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt @@ -51,11 +51,8 @@ import com.cornellappdev.uplift.ui.screens.report.ReportSubmittedScreen import com.cornellappdev.uplift.ui.viewmodels.classes.ClassDetailViewModel import com.cornellappdev.uplift.ui.viewmodels.gyms.GymDetailViewModel import com.cornellappdev.uplift.ui.viewmodels.nav.RootNavigationViewModel -import com.cornellappdev.uplift.ui.viewmodels.profile.CheckInUiState import com.cornellappdev.uplift.ui.viewmodels.profile.CheckInViewModel -import com.cornellappdev.uplift.util.ONBOARDING_FLAG import com.cornellappdev.uplift.ui.viewmodels.profile.ConfettiViewModel -import com.cornellappdev.uplift.ui.viewmodels.profile.ProfileViewModel import com.cornellappdev.uplift.util.CHECK_IN_FLAG import com.cornellappdev.uplift.util.PRIMARY_BLACK import com.cornellappdev.uplift.util.PRIMARY_YELLOW @@ -88,13 +85,6 @@ fun MainNavigationWrapper( val navController = rememberNavController() val systemUiController: SystemUiController = rememberSystemUiController() - val checkInUiState = checkInViewModel.collectUiStateValue() - val confettiUiState = confettiViewModel.collectUiStateValue() - - val profileViewModel: ProfileViewModel = hiltViewModel() - - - val yourShimmerTheme = defaultShimmerTheme.copy( shaderColors = listOf( Color.Unspecified.copy(alpha = 1f), @@ -107,7 +97,7 @@ fun MainNavigationWrapper( val items = listOfNotNull( BottomNavScreens.Home, BottomNavScreens.Classes, - BottomNavScreens.Profile.takeIf { ONBOARDING_FLAG } + BottomNavScreens.Profile ) systemUiController.setStatusBarColor(PRIMARY_YELLOW) @@ -251,7 +241,7 @@ fun MainNavigationWrapper( CapacityReminderScreen() } composable { - ProfileScreen(profileViewModel, {}, {}, {}) + ProfileScreen(toSettings = {}, toGoals = {}, toHistory = {}) } composable { MainReminderScreen() diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt index 2fa3f8fd..9f2b8010 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt @@ -1,36 +1,23 @@ package com.cornellappdev.uplift.ui.components.profile -import android.R.attr.fontFamily -import android.R.attr.fontWeight import android.net.Uri -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.cornellappdev.uplift.R import com.cornellappdev.uplift.ui.components.onboarding.PhotoPicker import com.cornellappdev.uplift.ui.components.onboarding.ScreenType -import com.cornellappdev.uplift.util.GRAY01 import com.cornellappdev.uplift.util.GRAY04 import com.cornellappdev.uplift.util.montserratFamily @@ -39,7 +26,7 @@ fun ProfileHeaderSection( name: String, gymDays: Int, streaks: Int, - netID: String, + netId: String, profilePictureUri: Uri?, onPhotoSelected: (Uri) -> Unit ){ @@ -53,7 +40,7 @@ fun ProfileHeaderSection( onPhotoSelected = onPhotoSelected, screenType = ScreenType.PROFILE ) - ProfileHeaderInfoDisplay(name, gymDays, streaks, netID, modifier = Modifier.weight(1f)) + ProfileHeaderInfoDisplay(name, gymDays, streaks, netId, modifier = Modifier.weight(1f)) } } @@ -146,6 +133,6 @@ private fun ProfileHeaderSectionPreview() { streaks = 15, profilePictureUri = null, onPhotoSelected = {}, - netID = "mv477" + netId = "mv477" ) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt index e97b7d12..067480b2 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt @@ -13,15 +13,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Icon import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusModifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt b/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt deleted file mode 100644 index 43854b80..00000000 --- a/app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.cornellappdev.uplift.ui.components.profile.workouts - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -private fun WorkoutsSectionContent( - workoutsCompleted: Int, - workoutGoal: Int, - daysOfMonth: List, - completedDays: List, - reminderItems: List, - historyItems: List, - navigateToGoalsSection: () -> Unit, - navigateToRemindersSection: () -> Unit, - navigateToHistorySection: () -> Unit -) { - Column( - modifier = Modifier.fillMaxSize() - ) { - GoalsSection( - workoutsCompleted = workoutsCompleted, - workoutGoal = workoutGoal, - daysOfMonth = daysOfMonth, - completedDays = completedDays, - onClick = navigateToGoalsSection, - ) - Spacer(modifier = Modifier.height(24.dp)) - - HistorySection( - historyItems = historyItems, - onClick = navigateToHistorySection, - modifier = Modifier.weight(1f) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt b/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt index 86ec4011..156f9800 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt @@ -1,6 +1,5 @@ package com.cornellappdev.uplift.ui.screens.profile -import android.R.attr.name import android.annotation.SuppressLint import android.net.Uri import androidx.compose.foundation.layout.Arrangement @@ -9,9 +8,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsEndWidth -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -23,27 +19,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.viewmodel.compose.viewModel import com.cornellappdev.uplift.R -import com.cornellappdev.uplift.data.repositories.ProfileRepository -import com.cornellappdev.uplift.data.repositories.UserInfoRepository -import com.cornellappdev.uplift.ui.components.general.UpliftTabRow import com.cornellappdev.uplift.ui.components.profile.workouts.GoalsSection import com.cornellappdev.uplift.ui.components.profile.workouts.HistoryItem import com.cornellappdev.uplift.ui.components.profile.workouts.HistorySection -import com.cornellappdev.uplift.ui.components.profile.workouts.MyRemindersSection import com.cornellappdev.uplift.ui.components.profile.ProfileHeaderSection import com.cornellappdev.uplift.ui.components.profile.workouts.ReminderItem import com.cornellappdev.uplift.ui.viewmodels.profile.ProfileViewModel @@ -90,7 +77,7 @@ fun ProfileScreen( streaks = uiState.activeStreak, profilePictureUri = uiState.profileImage?.let { Uri.parse(it) }, onPhotoSelected = {}, - netID = uiState.netId + netId = uiState.netId ) WorkoutsSectionContent( workoutsCompleted = uiState.workoutsCompleted, diff --git a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt index 8d24118d..72451fa6 100644 --- a/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt @@ -3,14 +3,12 @@ package com.cornellappdev.uplift.ui.viewmodels.profile import android.util.Log import androidx.lifecycle.viewModelScope import com.cornellappdev.uplift.data.repositories.ProfileRepository -import com.cornellappdev.uplift.data.repositories.UpliftApiRepository import com.cornellappdev.uplift.data.repositories.UserInfoRepository import com.cornellappdev.uplift.ui.components.profile.workouts.HistoryItem import com.cornellappdev.uplift.ui.viewmodels.UpliftViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import okhttp3.internal.format import java.time.DayOfWeek import java.time.Instant import java.time.LocalDate @@ -129,7 +127,7 @@ class ProfileViewModel @Inject constructor( .withZone(ZoneId.systemDefault()) private val formatDate = DateTimeFormatter - .ofPattern("MMMM d, yyyy • h:mm a") + .ofPattern("MMMM d, yyyy") .withLocale(Locale.US) .withZone(ZoneId.systemDefault()) } \ No newline at end of file