Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "GOOGLE_AUTH_CLIENT_ID", secretsProperties["GOOGLE_AUTH_CLIENT_ID"])
buildConfigField("String", "BACKEND_URL", secretsProperties['PROD_ENDPOINT'])
buildConfigField("boolean", "ONBOARDING_FLAG", "false")
buildConfigField("boolean", "CHECK_IN_FLAG", "false")
buildConfigField("boolean", "ONBOARDING_FLAG", "true")
buildConfigField("boolean", "CHECK_IN_FLAG", "true")
}
debug {
buildConfigField("String", "BACKEND_URL", secretsProperties['DEV_ENDPOINT'])
Expand All @@ -58,8 +58,8 @@ android {
"GOOGLE_AUTH_CLIENT_ID", secretsProperties["GOOGLE_AUTH_CLIENT_ID"]
)
signingConfig signingConfigs.debug
buildConfigField("boolean", "ONBOARDING_FLAG", "false")
buildConfigField("boolean", "CHECK_IN_FLAG", "false")
buildConfigField("boolean", "ONBOARDING_FLAG", "true")
buildConfigField("boolean", "CHECK_IN_FLAG", "true")
}
}
compileOptions {
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/graphql/User.graphql
Original file line number Diff line number Diff line change
@@ -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!) {
Expand Down
13 changes: 4 additions & 9 deletions app/src/main/graphql/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,11 @@ type User {
totalGymDays: Int!

"""
The start date of the most recent active streak, up until the current date.
The start datetime of the most recent active streak (midnight of the day in local timezone), up until the current date.
"""
streakStart: Date
streakStart: DateTime

workoutHistory: [Workout]
}

type Giveaway {
Expand Down Expand Up @@ -421,13 +423,6 @@ 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!

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.cornellappdev.uplift.data.models

import android.net.Uri


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<WorkoutDomain>,
val weeklyWorkoutDays: List<String>
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package com.cornellappdev.uplift.data.models
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
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.cornellappdev.uplift.data.models

data class WorkoutDomain(
val gymName: String,
val timestamp: Long
)
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Not sure if you need to separate into two variables if only used once


if (userId == null) {
Log.e("CheckInRepository", "Missing or invalid userId in DataStore: $userIdString")
return false
}

val time = Instant.now().toString()

return try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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 com.cornellappdev.uplift.data.models.ProfileData
import com.cornellappdev.uplift.data.models.WorkoutDomain
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.time.Instant
import javax.inject.Inject
import javax.inject.Singleton


@Singleton
class ProfileRepository @Inject constructor(
private val userInfoRepository: UserInfoRepository,
private val apolloClient: ApolloClient
) {
suspend fun getProfile(): Result<ProfileData> = runCatching {
val netId = userInfoRepository.getNetIdFromDataStore()
?: throw IllegalStateException("NetId missing")

val userResponse = apolloClient.query(
GetUserByNetIdQuery(netId)
).execute()

if (userResponse.hasErrors()) {
Log.e("ProfileRepo", "User query errors: ${userResponse.errors}")
throw IllegalStateException("User query failed")
}

val user = userResponse.data?.getUserByNetId?.firstOrNull()?.userFields
?: throw IllegalStateException("User not found")

val userId = user.id.toIntOrNull()
?: throw IllegalStateException("Invalid user ID: ${user.id}")

coroutineScope {
val workoutDeferred = async {
apolloClient.query(GetWorkoutsByIdQuery(userId)).execute()
}
val weeklyDeferred = async {
apolloClient.query(GetWeeklyWorkoutDaysQuery(userId)).execute()
}

val workoutResponse = workoutDeferred.await()
if (workoutResponse.hasErrors()) {
Log.e("ProfileRepo", "Workout query errors: ${workoutResponse.errors}")
throw IllegalStateException("Workout query failed")
}

val workouts = workoutResponse.data?.getWorkoutsById?.filterNotNull().orEmpty()

val workoutDomain = workouts.map {
WorkoutDomain(
gymName = it.workoutFields.gymName,
timestamp = Instant.parse(it.workoutFields.workoutTime.toString())
.toEpochMilli()
)
}

val weeklyResponse = weeklyDeferred.await()
if (weeklyResponse.hasErrors()) {
Log.e("ProfileRepo", "Weekly query errors: ${weeklyResponse.errors}")
throw IllegalStateException("Weekly workout days query failed")
}

val weeklyDays = weeklyResponse.data?.getWeeklyWorkoutDays?.filterNotNull().orEmpty()

ProfileData(
name = user.name,
netId = user.netId,
encodedImage = user.encodedImage,
totalGymDays = user.totalGymDays,
activeStreak = user.activeStreak,
maxStreak = user.maxStreak,
streakStart = user.streakStart?.toString(),
workoutGoal = user.workoutGoal ?: 0,
workouts = workoutDomain,
weeklyWorkoutDays = weeklyDays
)
}
}.onFailure { e ->
Log.e("ProfileRepo", "Failed to load profile", e)
}

suspend fun setWorkoutGoal(goal: Int): Result<Unit> = runCatching {
val userId = userInfoRepository.getUserIdFromDataStore()?.toIntOrNull()
?: throw IllegalStateException("Missing user ID")

val response = apolloClient
.mutation(
SetWorkoutGoalsMutation(
id = userId,
workoutGoal = goal
)
)
.execute()

if (response.hasErrors()) {
throw IllegalStateException("Goal update failed")
}
}.onFailure { e ->
Log.e("ProfileRepo", "Failed to update workout goal", e)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ class UserInfoRepository @Inject constructor(
else {
Log.d("UserInfoRepository", "Skipping goal upload")
}
storeUserFields(id, name, netId, email, skip, goal)
storeUserFields(
id = userFields.id,
username = userFields.name,
netId = userFields.netId,
email = userFields.email ?: email,
skip = skip,
goal = goal
)
Log.d("UserInfoRepositoryImpl", "User created successfully")
return true
} catch (e: Exception) {
Expand Down Expand Up @@ -107,6 +114,27 @@ class UserInfoRepository @Inject constructor(
}
}

suspend fun syncUserToDataStore(netId: String): Boolean {
return try {
val user = getUserByNetId(netId) ?: return false

storeUserFields(
id = user.id,
username = user.name,
netId = user.netId,
email = user.email,
skip = false,
goal = user.workoutGoal ?: 0
)

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(
Expand All @@ -120,6 +148,12 @@ class UserInfoRepository @Inject constructor(
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")
Expand Down Expand Up @@ -148,36 +182,6 @@ class UserInfoRepository @Inject constructor(
firebaseAuth.signOut()
}

private suspend fun storeId(id: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.ID] = id
}
}

private suspend fun storeUsername(username: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.USERNAME] = username
}
}

private suspend fun storeNetId(netId: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.NETID] = netId
}
}

private suspend fun storeEmail(email: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.EMAIL] = email
}
}

private suspend fun storeGoal(goal: Int) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.GOAL] = goal
}
}

suspend fun storeSkip(skip: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.SKIP] = skip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ 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.CheckInViewModel
import com.cornellappdev.uplift.util.ONBOARDING_FLAG
import com.cornellappdev.uplift.ui.viewmodels.profile.ConfettiViewModel
import com.cornellappdev.uplift.util.CHECK_IN_FLAG
import com.cornellappdev.uplift.util.PRIMARY_BLACK
Expand All @@ -77,8 +76,7 @@ fun MainNavigationWrapper(
classDetailViewModel: ClassDetailViewModel = hiltViewModel(),
rootNavigationViewModel: RootNavigationViewModel = hiltViewModel(),

) {

) {
val rootNavigationUiState = rootNavigationViewModel.collectUiStateValue()
val startDestination = rootNavigationUiState.startDestination

Expand All @@ -97,7 +95,7 @@ fun MainNavigationWrapper(
val items = listOfNotNull(
BottomNavScreens.Home,
BottomNavScreens.Classes,
BottomNavScreens.Profile.takeIf { ONBOARDING_FLAG }
BottomNavScreens.Profile
)

systemUiController.setStatusBarColor(PRIMARY_YELLOW)
Expand Down
Loading
Loading