Skip to content

Commit 338cee3

Browse files
Merge pull request #109 from cuappdev/preston/delete
Delete Button and login state management
2 parents 4e81463 + 0e27835 commit 338cee3

10 files changed

Lines changed: 315 additions & 37 deletions

File tree

app/src/main/java/com/cornellappdev/uplift/data/auth/SessionManager.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ class SessionManager @Inject constructor(
2424
)
2525

2626
// Call this after LoginUser or CreateUser mutations succeed
27-
fun startSession(userId: Int, name: String, email: String, access: String, refresh: String) {
27+
fun startSession(userId: Int, name: String, email: String, netId: String, access: String, refresh: String) {
2828
tokenManager.saveTokens(access, refresh)
29-
tokenManager.saveUserSession(userId, name, email)
29+
tokenManager.saveUserSession(userId, name, email, netId)
3030
}
3131

3232
// Call this for manual logout or when refresh fails
@@ -35,4 +35,6 @@ class SessionManager @Inject constructor(
3535
}
3636

3737
val userId: Int? get() = tokenManager.getUserId()
38+
39+
val netId: String? get() = tokenManager.getNetId()
3840
}

app/src/main/java/com/cornellappdev/uplift/data/auth/TokenManager.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,17 @@ class TokenManager @Inject constructor(@ApplicationContext private val context:
7070
_tokenFlow.value = null
7171
}
7272

73-
fun saveUserSession(userId: Int, username: String, userEmail: String) {
73+
fun saveUserSession(userId: Int, username: String, userEmail: String, netId: String) {
7474
sharedPreferences?.edit {
7575
putInt("user_id", userId)
7676
putString("username", username)
7777
putString("user_email", userEmail)
78+
putString("net_id", netId)
7879
}
7980
}
8081

8182
fun getUserId(): Int? = sharedPreferences?.takeIf { it.contains("user_id") }?.getInt("user_id", -1)
8283

84+
fun getNetId(): String? = sharedPreferences?.getString("net_id", null)
85+
8386
}

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

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.edit
88
import androidx.datastore.preferences.core.Preferences
99
import com.apollographql.apollo.ApolloClient
1010
import com.cornellappdev.uplift.CreateUserMutation
11+
import com.cornellappdev.uplift.DeleteUserMutation
1112
import com.cornellappdev.uplift.GetUserByNetIdQuery
1213
import com.cornellappdev.uplift.LoginUserMutation
1314
import com.cornellappdev.uplift.SetWorkoutGoalsMutation
@@ -80,6 +81,7 @@ class UserInfoRepository @Inject constructor(
8081
userId = id,
8182
name = name,
8283
email = email,
84+
netId = netId,
8385
access = accessToken,
8486
refresh = refreshToken
8587
)
@@ -106,10 +108,19 @@ class UserInfoRepository @Inject constructor(
106108
Log.e("UserInfoRepository", "Failed to log in: non-numeric user ID resulting in null '${userInfo.id}'")
107109
return false
108110
}
111+
storeUserFields(
112+
id = userInfo.id,
113+
username = userInfo.name,
114+
netId = netId,
115+
email = userInfo.email,
116+
goalSkip = false, // Defaulting to false on login if not known
117+
goal = userInfo.workoutGoal ?: 0
118+
)
109119
sessionManager.startSession(
110120
userId = id,
111121
name = userInfo.name,
112122
email = userInfo.email,
123+
netId = netId,
113124
access = loginData.accessToken,
114125
refresh = loginData.refreshToken
115126
)
@@ -204,8 +215,48 @@ class UserInfoRepository @Inject constructor(
204215
firebaseAuth.signInWithCredential(firebaseCredential).await()
205216
}
206217

207-
fun signOut() {
208-
firebaseAuth.signOut()
218+
suspend fun signOut() {
219+
try {
220+
firebaseAuth.signOut()
221+
} catch (e: Exception) {
222+
Log.e("UserInfoRepository", "Firebase signout failed: $e")
223+
} finally {
224+
sessionManager.logout()
225+
dataStore.edit { it.clear() }
226+
}
227+
}
228+
229+
suspend fun deleteAccount(): Boolean {
230+
return try {
231+
val userId = sessionManager.userId
232+
if (userId == null) {
233+
Log.e("UserInfoRepository", "Delete account failed: No user ID found in session")
234+
return false
235+
}
236+
237+
val response = apolloClient.mutation(DeleteUserMutation(userId = userId)).execute()
238+
if (response.hasErrors()) {
239+
Log.e("UserInfoRepository", "Server error during delete: ${response.errors}")
240+
return false
241+
}
242+
243+
// Want to log out no matter if the firebase deletes or not
244+
try {
245+
firebaseAuth.currentUser?.delete()?.await()
246+
Log.d("UserInfoRepository", "Firebase account deleted successfully")
247+
} catch (e: Exception) {
248+
Log.e("UserInfoRepository", "Firebase delete failed (User may need to re-login): $e")
249+
}
250+
251+
sessionManager.logout()
252+
dataStore.edit { it.clear() }
253+
254+
Log.d("UserInfoRepository", "Successfully signed out")
255+
true
256+
} catch (e: Exception) {
257+
Log.e("UserInfoRepository", "Error deleting account: $e")
258+
false
259+
}
209260
}
210261

211262
suspend fun storeSkip(skip: Boolean) {
@@ -236,7 +287,7 @@ class UserInfoRepository @Inject constructor(
236287
suspend fun getNetIdFromDataStore(): String? {
237288
return dataStore.data.map { preferences ->
238289
preferences[PreferencesKeys.NETID]
239-
}.firstOrNull()
290+
}.firstOrNull() ?: sessionManager.netId
240291
}
241292

242293
suspend fun getEmailFromDataStore(): String? {

app/src/main/java/com/cornellappdev/uplift/ui/components/goalsetting/DeleteDialog.kt

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.Color
3131
import androidx.compose.ui.res.painterResource
3232
import androidx.compose.ui.text.font.FontWeight
3333
import androidx.compose.ui.text.style.TextAlign
34+
import androidx.compose.ui.tooling.preview.Preview
3435
import androidx.compose.ui.unit.dp
3536
import androidx.compose.ui.unit.sp
3637
import androidx.compose.ui.window.Dialog
@@ -69,7 +70,15 @@ fun DeleteDialog(
6970
Box(
7071
modifier = Modifier.fillMaxSize(),
7172
) {
72-
CloseButton(onDismiss)
73+
CloseButton(modifier = Modifier
74+
.size(32.dp).
75+
clickable(
76+
interactionSource = remember { MutableInteractionSource() },
77+
indication = null
78+
) {
79+
onDismiss()
80+
}
81+
.offset(x = 206.dp, y = 12.dp))
7382
MainDialogColumn(onConfirm, onDismiss)
7483
}
7584
}
@@ -115,20 +124,13 @@ private fun MainDialogColumn(onConfirm: () -> Unit, onDismiss: () -> Unit) {
115124
}
116125

117126
@Composable
118-
private fun CloseButton(onDismiss: () -> Unit) {
127+
fun CloseButton(modifier: Modifier = Modifier,
128+
tint: Color = PRIMARY_BLACK) {
119129
Icon(
120130
imageVector = Icons.Default.Close,
121131
contentDescription = "Close",
122-
tint = PRIMARY_BLACK,
123-
modifier = Modifier
124-
.size(32.dp)
125-
.offset(x = 206.dp, y = 12.dp)
126-
.clickable(
127-
interactionSource = remember { MutableInteractionSource() },
128-
indication = null
129-
) {
130-
onDismiss()
131-
}
132+
tint = tint,
133+
modifier = modifier
132134
)
133135
}
134136

@@ -154,4 +156,13 @@ private fun ConfirmButton(onConfirm: () -> Unit) {
154156
textAlign = TextAlign.Center
155157
)
156158
}
159+
}
160+
161+
@Preview
162+
@Composable
163+
private fun DeleteDialogPreview() {
164+
DeleteDialog(
165+
deleteDialogOpen = true ,
166+
onConfirm = {}
167+
) { }
157168
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.cornellappdev.uplift.ui.components.profile
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.interaction.MutableInteractionSource
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.layout.width
12+
import androidx.compose.foundation.shape.RoundedCornerShape
13+
import androidx.compose.material3.Icon
14+
import androidx.compose.material3.Surface
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.remember
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.res.painterResource
22+
import androidx.compose.ui.text.TextStyle
23+
import androidx.compose.ui.text.font.FontWeight
24+
import androidx.compose.ui.text.style.TextAlign
25+
import androidx.compose.ui.tooling.preview.Preview
26+
import androidx.compose.ui.unit.dp
27+
import androidx.compose.ui.unit.sp
28+
import androidx.compose.ui.window.Dialog
29+
import com.cornellappdev.uplift.R
30+
import com.cornellappdev.uplift.ui.components.general.UpliftButton
31+
import com.cornellappdev.uplift.ui.components.goalsetting.CloseButton
32+
import com.cornellappdev.uplift.ui.theme.AppColors
33+
import com.cornellappdev.uplift.util.montserratFamily
34+
35+
@Composable
36+
fun DeleteAccountConfirmationDialog(
37+
onDismiss: () -> Unit,
38+
onConfirm: () -> Unit
39+
) {
40+
Dialog(onDismissRequest = onDismiss) {
41+
Surface(
42+
shape = RoundedCornerShape(24.dp),
43+
color = AppColors.White,
44+
modifier = Modifier.width(250.dp)
45+
) {
46+
Box(
47+
modifier = Modifier.fillMaxWidth()
48+
) {
49+
CloseButton(
50+
tint = AppColors.Black.copy(alpha = 0.4f),
51+
modifier = Modifier
52+
.align(Alignment.TopEnd)
53+
.padding(top = 12.dp, end = 12.dp)
54+
.size(24.dp)
55+
.clickable(
56+
interactionSource = remember { MutableInteractionSource() },
57+
indication = null
58+
) {
59+
onDismiss()
60+
},
61+
)
62+
63+
Column(
64+
modifier = Modifier
65+
.fillMaxWidth()
66+
.padding(20.dp),
67+
horizontalAlignment = Alignment.CenterHorizontally,
68+
verticalArrangement = Arrangement.spacedBy(20.dp)
69+
) {
70+
Icon(
71+
painter = painterResource(id = R.drawable.ic_trash_2),
72+
contentDescription = "Delete account",
73+
modifier = Modifier.size(36.dp),
74+
tint = AppColors.Black
75+
)
76+
77+
Column(
78+
horizontalAlignment = Alignment.CenterHorizontally,
79+
verticalArrangement = Arrangement.spacedBy(12.dp)
80+
) {
81+
Text(
82+
text = "Are you sure you want to delete your Uplift account?",
83+
style = TextStyle(
84+
color = AppColors.Black,
85+
textAlign = TextAlign.Center,
86+
fontFamily = montserratFamily,
87+
fontSize = 14.sp,
88+
fontWeight = FontWeight.W400,
89+
lineHeight = 18.sp
90+
),
91+
modifier = Modifier.padding(horizontal = 8.dp)
92+
)
93+
94+
Text(
95+
text = "All workout data will be lost.",
96+
style = TextStyle(
97+
color = AppColors.Black,
98+
textAlign = TextAlign.Center,
99+
fontFamily = montserratFamily,
100+
fontSize = 14.sp,
101+
fontWeight = FontWeight.W500,
102+
lineHeight = 18.sp
103+
)
104+
)
105+
}
106+
107+
Column(
108+
horizontalAlignment = Alignment.CenterHorizontally,
109+
verticalArrangement = Arrangement.spacedBy(12.dp)
110+
) {
111+
UpliftButton(
112+
onClick = onConfirm,
113+
text = "Delete",
114+
modifier = Modifier
115+
.fillMaxWidth(),
116+
height = 41.dp,
117+
containerColor = AppColors.Black,
118+
contentColor = AppColors.White,
119+
fontSize = 16f,
120+
elevation = 0.dp
121+
)
122+
123+
UpliftButton(
124+
onClick = onDismiss,
125+
text = "Back",
126+
modifier = Modifier.align(Alignment.CenterHorizontally),
127+
containerColor = Color.Transparent,
128+
contentColor = AppColors.Black,
129+
fontSize = 14f,
130+
elevation = 0.dp
131+
)
132+
}
133+
}
134+
}
135+
}
136+
}
137+
}
138+
139+
@Preview
140+
@Composable
141+
fun DeleteAccountConfirmationDialogPreview() {
142+
DeleteAccountConfirmationDialog(
143+
onDismiss = {},
144+
onConfirm = {}
145+
)
146+
}

0 commit comments

Comments
 (0)