Skip to content

Commit 8f7bb3c

Browse files
added token manager and error handling
1 parent 39fc3a7 commit 8f7bb3c

5 files changed

Lines changed: 118 additions & 54 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ android {
5858
"GOOGLE_AUTH_CLIENT_ID", secretsProperties["GOOGLE_AUTH_CLIENT_ID"]
5959
)
6060
signingConfig signingConfigs.debug
61-
buildConfigField("boolean", "ONBOARDING_FLAG", "true")
61+
buildConfigField("boolean", "ONBOARDING_FLAG", "false")
6262
buildConfigField("boolean", "CHECK_IN_FLAG", "false")
6363
}
6464
}
@@ -127,6 +127,7 @@ dependencies {
127127
implementation 'com.google.firebase:firebase-auth'
128128
implementation 'com.google.firebase:firebase-messaging'
129129
implementation("com.google.firebase:firebase-analytics")
130+
implementation "androidx.security:security-crypto:1.1.0-alpha06"
130131
}
131132

132133
apollo {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.cornellappdev.uplift.data.repositories
2+
3+
import android.content.Context
4+
import androidx.security.crypto.EncryptedSharedPreferences
5+
import androidx.security.crypto.MasterKey
6+
import androidx.core.content.edit
7+
8+
class TokenManager(context: Context) {
9+
private val fileName = "encrypted_tokens"
10+
11+
// 1. Create or retrieve the Master Key for encryption
12+
private val masterKey = MasterKey.Builder(context)
13+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
14+
.build()
15+
16+
// 2. Initialize EncryptedSharedPreferences
17+
private val sharedPreferences = EncryptedSharedPreferences.create(
18+
context,
19+
fileName,
20+
masterKey,
21+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
22+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
23+
)
24+
25+
fun saveTokens(accessToken: String, refreshToken: String) {
26+
sharedPreferences.edit().apply {
27+
putString("access_token", accessToken)
28+
putString("refresh_token", refreshToken)
29+
apply()
30+
}
31+
}
32+
33+
fun getAccessToken(): String? = sharedPreferences.getString("access_token", null)
34+
35+
fun getRefreshToken(): String? = sharedPreferences.getString("refresh_token", null)
36+
37+
fun clearTokens() {
38+
sharedPreferences.edit { clear() }
39+
}
40+
}

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

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class UserInfoRepository @Inject constructor(
2424
private val firebaseAuth: FirebaseAuth,
2525
private val apolloClient: ApolloClient,
2626
private val dataStore: DataStore<Preferences>,
27+
private val tokenManager: TokenManager
2728
){
2829

2930
suspend fun createUser(email: String, name: String, netId: String, skip: Boolean, goal: Int): Boolean {
@@ -36,7 +37,7 @@ class UserInfoRepository @Inject constructor(
3637
)
3738
).execute()
3839
val userFields = response.data?.createUser?.userFields
39-
if (userFields == null) {
40+
if (response.hasErrors() || userFields == null) {
4041
Log.e("UserInfoRepository", "Server error: ${response.errors}")
4142
return false
4243
}
@@ -53,44 +54,65 @@ class UserInfoRepository @Inject constructor(
5354
}
5455
val accessToken = loginData.accessToken
5556
val refreshToken = loginData.refreshToken
56-
storeId(id)
57-
storeNetId(netId)
58-
storeUsername(name)
59-
storeEmail(email)
60-
storeSkip(skip)
61-
storeAccessToken(accessToken)
62-
storeRefreshToken(refreshToken)
57+
tokenManager.saveTokens(accessToken, refreshToken)
6358
if (!skip) {
64-
storeGoal(goal)
65-
val goalResponse = apolloClient.mutation(
66-
SetWorkoutGoalsMutation(
67-
id = id.toInt(),
68-
workoutGoal = goal
69-
)
70-
)
71-
.addHttpHeader("Authorization", "Bearer $accessToken")
72-
.execute()
73-
if (goalResponse.hasErrors()) {
74-
Log.e("UserInfoRepository", "Failed to set goal: ${goalResponse.errors}")
75-
return false
76-
}
59+
val numericId = id.toIntOrNull()
60+
uploadGoal(numericId, goal)
7761
}
78-
Log.d("UserInfoRepositoryImpl", "User created successfully" + response.data)
62+
storeUserFields(id, name, netId, email, skip, accessToken, refreshToken, goal)
63+
Log.d("UserInfoRepositoryImpl", "User created successfully")
7964
return true
8065
} catch (e: Exception) {
8166
Log.e("UserInfoRepositoryImpl", "Error creating user: $e")
8267
return false
8368
}
8469
}
8570

71+
suspend fun uploadGoal(id:Int?, goal: Int): Boolean {
72+
if (id == null) {
73+
Log.e("UserInfoRepository", "Failed to set goal: non-numeric user ID '$id'")
74+
return false
75+
}
76+
val goalResponse = apolloClient.mutation(
77+
SetWorkoutGoalsMutation(
78+
id = id,
79+
workoutGoal = goal
80+
)
81+
)
82+
// Change so that it uses auth interceptor
83+
.addHttpHeader("Authorization", "Bearer ${tokenManager.getAccessToken()}")
84+
.execute()
85+
if (goalResponse.hasErrors()) {
86+
Log.e("UserInfoRepository", "Failed to set goal: ${goalResponse.errors}")
87+
return false
88+
}
89+
return true
90+
}
91+
92+
93+
suspend fun storeUserFields(id: String, username: String, netId: String, email: String, skip: Boolean, accessToken: String, refreshToken: String, goal: Int) {
94+
dataStore.edit { preferences ->
95+
preferences[PreferencesKeys.ID] = id
96+
preferences[PreferencesKeys.NETID] = netId
97+
preferences[PreferencesKeys.USERNAME] = username
98+
preferences[PreferencesKeys.EMAIL] = email
99+
preferences[PreferencesKeys.SKIP] = skip
100+
preferences[PreferencesKeys.ACCESS_TOKEN] = accessToken
101+
preferences[PreferencesKeys.REFRESH_TOKEN] = refreshToken
102+
if (!skip) {
103+
preferences[PreferencesKeys.GOAL] = goal
104+
}
105+
}
106+
}
107+
86108
suspend fun getUserByNetId(netId: String): UserInfo? {
87109
try {
88110
val response = apolloClient.query(
89111
GetUserByNetIdQuery(
90112
netId = netId
91113
)
92-
).executeV3()
93-
val user = response.data?.getUserByNetId?.get(0)?.userFields ?: return null
114+
).execute()
115+
val user = response.data?.getUserByNetId?.firstOrNull()?.userFields ?: return null
94116
return UserInfo(
95117
id = user.id,
96118
name = user.name,
@@ -107,7 +129,7 @@ class UserInfoRepository @Inject constructor(
107129
return firebaseAuth.currentUser != null
108130
}
109131

110-
suspend fun getFirebaseUser(): FirebaseUser? {
132+
fun getFirebaseUser(): FirebaseUser? {
111133
return firebaseAuth.currentUser
112134
}
113135

app/src/main/java/com/cornellappdev/uplift/ui/screens/reminders/WorkoutReminderScreen.kt

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import androidx.compose.foundation.background
44
import androidx.compose.foundation.clickable
55
import androidx.compose.foundation.layout.Arrangement
66
import androidx.compose.foundation.layout.Column
7-
import androidx.compose.foundation.layout.Spacer
87
import androidx.compose.foundation.layout.fillMaxSize
98
import androidx.compose.foundation.layout.fillMaxWidth
109
import androidx.compose.foundation.layout.padding
@@ -112,7 +111,7 @@ fun WorkoutReminderScreen(
112111
selectedReminder = selectedReminder,
113112
addNewReminderState = addNewReminderState,
114113
deleteDialogOpen = deleteDialogOpen,
115-
goalValue = goalValue.toInt(),
114+
goalValue = goalValue,
116115
isOnboarding = isOnboarding,
117116
// Pass callbacks to update the state defined above
118117
onSelectedReminderChange = { selectedReminder = it },
@@ -133,7 +132,7 @@ private fun WorkoutReminderContent(
133132
selectedReminder: Reminder?,
134133
addNewReminderState: Boolean,
135134
deleteDialogOpen: Boolean,
136-
goalValue: Int,
135+
goalValue: Float,
137136
isOnboarding: Boolean,
138137
onSelectedReminderChange: (Reminder?) -> Unit,
139138
onAddNewReminderStateChange: (Boolean) -> Unit,
@@ -152,35 +151,32 @@ private fun WorkoutReminderContent(
152151
withBack = true
153152
)
154153
},
154+
bottomBar = {
155+
if (isOnboarding) {
156+
OnboardingButtons(onNext, onSkip)
157+
}
158+
},
155159
modifier = Modifier.fillMaxSize(),
156160
) { padding ->
157161
Column(
158162
modifier = Modifier
159163
.background(color = Color.White)
160-
.padding(top = padding.calculateTopPadding())
164+
.padding(padding)
161165
.fillMaxSize()
162-
.verticalScroll(rememberScrollState()),
163-
verticalArrangement = Arrangement.SpaceBetween
166+
.verticalScroll(rememberScrollState())
164167
) {
165-
Column(modifier = Modifier.fillMaxSize()) {
166-
GoalSlider(value = goalValue.toFloat(), onValueChange = onGoalValueChange)
168+
GoalSlider(value = goalValue, onValueChange = onGoalValueChange)
167169

168-
if (!isOnboarding) {
169-
WorkoutReminders(
170-
selectedReminder = selectedReminder,
171-
onSelectedReminderChange = onSelectedReminderChange,
172-
reminders = reminders,
173-
onRemindersChange = onRemindersChange,
174-
addNewReminderState = addNewReminderState,
175-
onAddNewReminderStateChange = onAddNewReminderStateChange,
176-
openDelete = { onDeleteDialogOpenChange(true) }
177-
)
178-
}
179-
}
180-
181-
Spacer(modifier = Modifier.weight(1f))
182-
if (isOnboarding) {
183-
OnboardingButtons(onNext, onSkip)
170+
if (!isOnboarding) {
171+
WorkoutReminders(
172+
selectedReminder = selectedReminder,
173+
onSelectedReminderChange = onSelectedReminderChange,
174+
reminders = reminders,
175+
onRemindersChange = onRemindersChange,
176+
addNewReminderState = addNewReminderState,
177+
onAddNewReminderStateChange = onAddNewReminderStateChange,
178+
openDelete = { onDeleteDialogOpenChange(true) }
179+
)
184180
}
185181
}
186182

@@ -263,4 +259,4 @@ fun WorkoutReminderScreenPreview() {
263259
onGoalValueChange = { sliderVal = it },
264260
onBackClick = {},
265261
)
266-
}
262+
}

app/src/main/java/com/cornellappdev/uplift/ui/viewmodels/onboarding/ProfileCreationViewModel.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@ class ProfileCreationViewModel @Inject constructor(
3737
private fun createUser() = viewModelScope.launch {
3838
val user = userInfoRepository.getFirebaseUser()
3939
val name = user?.displayName ?: ""
40-
val email = user?.email ?: ""
41-
val netId = email.substring(0, email.indexOf('@'))
40+
val email = user?.email
41+
if (email.isNullOrBlank()) {
42+
Log.e("Error", "Cannot create user: missing or blank email")
43+
userInfoRepository.signOut()
44+
return@launch
45+
}
46+
47+
val netId = email.substringBefore("@")
4248
val isSkipped = getStateValue().isGoalSkipped
4349
var goal = 0
4450
if (!isSkipped) {
@@ -76,7 +82,6 @@ class ProfileCreationViewModel @Inject constructor(
7682
}
7783
createUser()
7884
}
79-
}
8085

8186
fun onNext() {
8287
createUser()

0 commit comments

Comments
 (0)