Conversation
There was a problem hiding this comment.
Pull request overview
Implements the new Profile page end-to-end (UI + ViewModel + repository) and updates GraphQL schema/queries/models to match the latest backend changes.
Changes:
- Added profile data loading via
ProfileRepository+ProfileViewModeland wired it into the Profile screen UI. - Updated GraphQL schema and
User/Workoutfragments + mutations to align with backend changes (e.g., workoutGoal asInt). - Added/updated supporting UI components (history empty state, weekly progress, progress arc) and small logging/debug improvements in Check-In and login flows.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/res/drawable/ic_bag.png | Adds an empty-state icon for workout history. |
| app/src/main/java/com/cornellappdev/uplift/util/Functions.kt | Adds Calendar.timeAgoString() helper for history “time ago” text. |
| app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/ProfileViewModel.kt | New Profile VM: loads profile + formats workout history + computes weekly progress state. |
| app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/profile/CheckInViewModel.kt | Logs success/failure of backend workout logging. |
| app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/onboarding/LoginViewModel.kt | Syncs existing user data into DataStore before navigating to Home. |
| app/src/main/java/com/cornellappdev/uplift/ui/screens/profile/ProfileScreen.kt | Reworks Profile screen to use ProfileViewModel state and new navigation callbacks. |
| app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutSection.kt | Adds a workouts section composable (currently duplicated). |
| app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WorkoutProgressArc.kt | Improves arc rendering for zero/complete goal cases; tweaks labels. |
| app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/WeeklyProgressTracker.kt | Adds guard for insufficient day data. |
| app/src/main/java/com/cornellappdev/uplift/ui/components/profile/workouts/HistorySection.kt | Adds “time ago” display and empty state UI for history. |
| app/src/main/java/com/cornellappdev/uplift/ui/components/profile/ProfileHeaderSection.kt | Updates header to display netId and handle long names. |
| app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt | Wires ProfileScreen into navigation and introduces ProfileViewModel creation in wrapper. |
| app/src/main/java/com/cornellappdev/uplift/data/repositories/UserInfoRepository.kt | Fixes create-user DataStore writes; adds syncUserToDataStore; expands user model mapping. |
| app/src/main/java/com/cornellappdev/uplift/data/repositories/ProfileRepository.kt | New repository fetching user/workouts/weekly days and setting workout goal. |
| app/src/main/java/com/cornellappdev/uplift/data/repositories/CheckInRepository.kt | Improves userId parsing/logging and adds extra error logging for logWorkout. |
| app/src/main/java/com/cornellappdev/uplift/data/models/UserInfo.kt | Expands UserInfo model to include profile-related fields. |
| app/src/main/graphql/schema.graphqls | Updates schema for user streak/goal fields, workout fields, and mutations. |
| app/src/main/graphql/User.graphql | Updates fragments/queries/mutations (e.g., workoutGoal: Int, adds workout gymName). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,10 +1,18 @@ | |||
| package com.cornellappdev.uplift.data.models | |||
| import com.cornellappdev.uplift.type.DateTime | |||
There was a problem hiding this comment.
com.cornellappdev.uplift.type.DateTime is imported but not used in this data class. Remove the unused import to avoid lint warnings and keep the model minimal.
| import com.cornellappdev.uplift.type.DateTime |
| val historyItems = profile.workouts.map { | ||
| HistoryItem( | ||
| gymName = it.gymName, | ||
| time = formatTime.format( | ||
| Instant.ofEpochMilli(it.timestamp) | ||
| ), | ||
| date = formatDate.format( | ||
| Instant.ofEpochMilli(it.timestamp) | ||
| ), |
There was a problem hiding this comment.
HistoryItem is built with both time (formatted as h:mm a) and date formatted as MMMM d, yyyy • h:mm a. In HistoryItemRow the UI renders "$date · $time", which will duplicate the time portion (e.g., "March 29, 2024 • 1:00 PM · 1:00 PM"). Either remove the time from formatDate or stop appending time in the row.
| val checkInUiState = checkInViewModel.collectUiStateValue() | ||
| val confettiUiState = confettiViewModel.collectUiStateValue() | ||
|
|
There was a problem hiding this comment.
checkInViewModel and confettiViewModel are referenced here but are not declared in MainNavigationWrapper (they’re only created later inside the Box when CHECK_IN_FLAG is true). This will not compile; remove these lines or instantiate the view models before collecting their UI state (and avoid duplicating the later declarations).
| val checkInUiState = checkInViewModel.collectUiStateValue() | |
| val confettiUiState = confettiViewModel.collectUiStateValue() |
| 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 |
There was a problem hiding this comment.
import androidx.compose.ui.focus.focusModifier (and some of the other new imports around it) is unused here, and focusModifier is not part of the public Compose API in recent versions—this may fail compilation. Please remove the unused/invalid imports to avoid build errors.
| activeStreak = user.activeStreak ?: 0, | ||
| maxStreak = user.maxStreak ?: 0, |
There was a problem hiding this comment.
In the updated GraphQL schema, activeStreak/maxStreak are now non-null (Int!). After regeneration, these fields will be non-null Kotlin Ints, so using the Elvis operator (user.activeStreak ?: 0) will not compile. Update this mapping to treat them as non-null (and only use null-coalescing for fields that remain nullable, e.g. workoutGoal).
| activeStreak = user.activeStreak ?: 0, | |
| maxStreak = user.maxStreak ?: 0, | |
| activeStreak = user.activeStreak, | |
| maxStreak = user.maxStreak, |
| val profileViewModel: ProfileViewModel = hiltViewModel() | ||
|
|
There was a problem hiding this comment.
ProfileViewModel is being created in MainNavigationWrapper via hiltViewModel(), even though this file’s own comment says future view models should be added in the screen they’re used in. Consider removing this and letting ProfileScreen create its own hiltViewModel() (or instantiate it inside the composable<UpliftRootRoute.Profile> block) so the ViewModel is properly scoped to the Profile destination.
| 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 |
There was a problem hiding this comment.
There are several unused imports here (e.g. android.R.attr.name, windowInsetsEndWidth, rememberScrollState, verticalScroll, viewModel, ProfileRepository, UserInfoRepository, etc.). Please remove unused imports to keep the file clean and avoid lint/CI failures if unused-imports are enforced.
| import androidx.compose.ui.unit.dp | ||
|
|
||
| @Composable | ||
| private fun WorkoutsSectionContent( | ||
| workoutsCompleted: Int, | ||
| workoutGoal: Int, | ||
| daysOfMonth: List<Int>, | ||
| completedDays: List<Boolean>, | ||
| reminderItems: List<ReminderItem>, | ||
| historyItems: List<HistoryItem>, | ||
| 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 |
There was a problem hiding this comment.
This new file duplicates the WorkoutsSectionContent composable that is already defined/used in ProfileScreen.kt, but this version is private and is not referenced anywhere. Consider deleting this file or making it the single shared implementation to avoid dead code and future drift between two copies.
| import androidx.compose.ui.unit.dp | |
| @Composable | |
| private fun WorkoutsSectionContent( | |
| workoutsCompleted: Int, | |
| workoutGoal: Int, | |
| daysOfMonth: List<Int>, | |
| completedDays: List<Boolean>, | |
| reminderItems: List<ReminderItem>, | |
| historyItems: List<HistoryItem>, | |
| 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) | |
| ) | |
| } | |
| } | |
| import androidx.compose.ui.unit.dp |
| import android.R.attr.fontFamily | ||
| import android.R.attr.fontWeight |
There was a problem hiding this comment.
android.R.attr.fontFamily and android.R.attr.fontWeight are imported but not used. Please remove these unused imports (and prefer Compose’s FontFamily/FontWeight types when needed) to keep the file clean.
| import android.R.attr.fontFamily | |
| import android.R.attr.fontWeight |
| 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}") |
There was a problem hiding this comment.
Logging the full GraphQL response.data on failures can leak user/workout data into logs (which may be captured on devices or in crash reports). Prefer logging only the error messages/codes, or gate verbose response logging behind a debug-only flag.
| Log.e("CheckInRepository", "LogWorkout response data=${response.data}") |
# Conflicts: # app/src/main/java/com/cornellappdev/uplift/ui/MainNavigationWrapper.kt
10f1633 to
361c307
Compare
Overview
Profile Page implementation that aligns with the latest backend schema and new designs.
Changes Made
Updated the schema and user GraphQL queries/models to match the latest backend changes
Updated the User data model/class accordingly
Reworked the profile page UI and components to match the latest designs
Implemented the Profile ViewModel and repository logic to load profile data from backend
Added additional testing/debugging work around workout logging through the Check-In ViewModel and repository
Test Coverage
Manually tested
Next Steps (delete if not applicable)
Further test profile page and workout history once logWorkout is fully authenticated and working
Add networking and logic for settings and full workout history
Add loading page
Screenshots
Screen Shot\