From 02b6addf43eae16384c95fa68d688b3b1a91a48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alperen=20Da=C4=9F=C4=B1?= <111731140+alperenDagi@users.noreply.github.com> Date: Tue, 28 Nov 2023 00:11:30 +0300 Subject: [PATCH] Revert "Feature/mobile 360 update user profiles" --- resq/mobile/ResQ/app/build.gradle.kts | 9 +- .../com/cmpe451/resq/data/models/Profile.kt | 38 +- .../cmpe451/resq/data/remote/ResqService.kt | 74 +- .../java/com/cmpe451/resq/ui/theme/Color.kt | 4 +- .../resq/ui/views/screens/ProfileScreen.kt | 774 ++++++++---------- .../resq/viewmodels/ProfileViewModel.kt | 31 +- 6 files changed, 378 insertions(+), 552 deletions(-) diff --git a/resq/mobile/ResQ/app/build.gradle.kts b/resq/mobile/ResQ/app/build.gradle.kts index 21a0806d..4ab1bd7c 100644 --- a/resq/mobile/ResQ/app/build.gradle.kts +++ b/resq/mobile/ResQ/app/build.gradle.kts @@ -101,12 +101,6 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") - - // Coil - implementation("io.coil-kt:coil-compose:2.4.0") - -} - } secrets{ @@ -114,5 +108,4 @@ secrets{ ignoreList.add("keyToIgnore") // Ignore the key "keyToIgnore" ignoreList.add("sdk.*") // Ignore all keys matching the regexp "sdk.*" -} - +} \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Profile.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Profile.kt index 11bab59d..939051bd 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Profile.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Profile.kt @@ -3,33 +3,25 @@ package com.cmpe451.resq.data.models data class ProfileData( var name: String?, var surname: String?, - var birthdate: String?, - var gender:String?, - var bloodType: String?, + var email: String?, + var roles: List?, + var selectedRole: String?, var phoneNumber: String?, var country: String?, var city: String?, var state: String?, - var weight: Int?, - var height: Int?, - val emailConfirmed: Boolean? = false, - val privacyPolicyAccepted: Boolean? = false, -) - - -data class UserInfoRequest( - var name: String?, - var surname: String?, - var birthdate: String?, - var gender:String?, var bloodType: String?, - var phoneNumber: String?, - var country: String?, - var city: String?, - var state: String?, - var weight: Int?, - var height: Int?, - val emailConfirmed: Boolean? = false, - val privacyPolicyAccepted: Boolean? = false, + var weight: String?, + var gender:String?, + var height: String?, + var year: String?, + var month: String?, + var day: String? ) +data class UserInfoResponse( + val name: String, + val surname: String, + val email: String, + val roles: List +) \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/remote/ResqService.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/remote/ResqService.kt index 1aeac6be..33aefaf5 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/remote/ResqService.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/remote/ResqService.kt @@ -14,7 +14,7 @@ import com.cmpe451.resq.data.models.Need import com.cmpe451.resq.data.models.ProfileData import com.cmpe451.resq.data.models.RegisterRequestBody import com.cmpe451.resq.data.models.UserInfoRequest - +import com.cmpe451.resq.data.models.UserInfo import okhttp3.ResponseBody import retrofit2.Response import retrofit2.Retrofit @@ -49,7 +49,6 @@ interface NeedService { ): Response - @GET("need/filterByDistance") fun filterNeedByDistance( @Query("latitude") latitude: Double, @@ -70,15 +69,12 @@ interface AuthService { } interface ProfileService { - @GET("profile/getProfileInfo") + @GET("user/getUserInfo") suspend fun getUserInfo( @Query("userId") userId: Int, @Header("Authorization") jwtToken: String, @Header("X-Selected-Role") role: String - - ): Response - - + ): Response @POST("user/requestRole") suspend fun selectRole( @@ -89,14 +85,14 @@ interface ProfileService { ): Response - @POST("profile/updateProfile") suspend fun updateProfile( @Query("userId") userId: Int, @Header("Authorization") jwtToken: String, @Header("X-Selected-Role") role: String, @Body request: UserInfoRequest - ): Response + ): Response + } class ResqService(appContext: Context) { @@ -179,16 +175,17 @@ class ResqService(appContext: Context) { if (birthDate.isNullOrBlank()) { return null } - return try { + + try { val date = LocalDate.parse(birthDate) val year = date.year.toString() val month = date.monthValue.toString() val day = date.dayOfMonth.toString() - Triple(year, month, day) + return Triple(year, month, day) } catch (e: DateTimeParseException) { //TO DO Handle parsing error if needed - null + return null } } @@ -205,48 +202,43 @@ class ResqService(appContext: Context) { role = selectedRole ) - return ProfileData( - name = response.body()?.name, - surname = response.body()?.surname, - city = response.body()?.city, - country = response.body()?.country, - gender = response.body()?.gender, - bloodType = response.body()?.bloodType, - height = response.body()?.height, - weight = response.body()?.weight, - phoneNumber = response.body()?.phoneNumber, - state = response.body()?.state, - emailConfirmed = response.body()?.emailConfirmed, - privacyPolicyAccepted = response.body()?.privacyPolicyAccepted, - birthdate = response.body()?.birthdate.toString(), + val parsedDate = parseBirthDate(response.body()?.birth_date) + val (parsedYear, parsedMonth, parsedDay) = parsedDate ?: Triple("", "", "") + val profileData = ProfileData( + name = response.body()?.name, surname = response.body()?.surname, + email = response.body()?.email, + roles = response.body()?.roles, selectedRole = selectedRole, + year = parsedYear, month = parsedMonth, day = parsedDay, + city = response.body()?.city, country = response.body()?.country, + gender = response.body()?.gender, bloodType = response.body()?.bloodType, height = response.body()?.height.toString(), weight = response.body()?.weight.toString(), + phoneNumber = response.body()?.phoneNumber, state = response.body()?.state, + emailConfirmed = response.body()?.emailConfirmed, privacyPolicyAccepted = response.body()?.privacyPolicyAccepted + ) + return profileData } @RequiresApi(Build.VERSION_CODES.O) - suspend fun updateUserData(profileData: ProfileData): Response { + suspend fun updateUserData(profileData: ProfileData): Response{ val token = userSessionManager.getUserToken() ?: "" val userId = userSessionManager.getUserId() val selectedRole = userSessionManager.getSelectedRole() ?: "" - + val formattedBirthDate = profileData.getFormattedBirthDate() val request = UserInfoRequest( - name = profileData.name ?: "", + name = profileData.name ?: "", surname = profileData.surname ?: "", - birthdate = null, + email = profileData.email ?: "", + roles = profileData.roles ?: listOf(), + birth_date = formattedBirthDate, country = profileData.country ?: "", city = profileData.city ?: "", state = profileData.state ?: "", bloodType = profileData.bloodType ?: "", - height = profileData.height, - weight = profileData.weight, + height = profileData.height?.toIntOrNull(), + weight = profileData.weight?.toIntOrNull(), gender = profileData.gender ?: "", phoneNumber = profileData.phoneNumber ?: "", ) - return profileService.updateProfile( - userId = userId, - jwtToken = "Bearer $token", - role = selectedRole, - request = request - ) val response = profileService.updateProfile( userId = userId, jwtToken = "Bearer $token", @@ -254,8 +246,11 @@ class ResqService(appContext: Context) { request = request ) return response + + + } - + suspend fun selectRole(requestedRole: String): Response { val userId = userSessionManager.getUserId() val token = userSessionManager.getUserToken() ?: "" @@ -267,6 +262,7 @@ class ResqService(appContext: Context) { jwtToken = "Bearer $token", role = requestedRole ) + return response } } diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/theme/Color.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/theme/Color.kt index fa61ec70..4043aa33 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/theme/Color.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/theme/Color.kt @@ -16,6 +16,4 @@ val LightGreen = Color(0xff20df7f) val ResourceColor = Color(0xFF397FE7) val RequestColor = Color(0xFFB356AF) -val MyTasksColor = Color(0XFFE7A139) -val OngoingTasksColor = Color(0xFFE16834) -val BackgroundColor = Color(0xFFEFEFEF) +val BackgroundColor = Color(0xFFEFEFEF) \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ProfileScreen.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ProfileScreen.kt index 896349e3..b04adc24 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ProfileScreen.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ProfileScreen.kt @@ -1,10 +1,6 @@ package com.cmpe451.resq.ui.views.screens import android.content.Context -import android.net.Uri import android.os.Build -import android.util.Log -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -21,7 +17,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -54,13 +49,10 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType @@ -69,64 +61,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import coil.compose.rememberAsyncImagePainter -import com.cmpe451.resq.data.manager.UserSessionManager import com.cmpe451.resq.data.models.ProfileData -import com.cmpe451.resq.ui.theme.MyTasksColor -import com.cmpe451.resq.ui.theme.OngoingTasksColor -import com.cmpe451.resq.ui.theme.RequestColor -import com.cmpe451.resq.ui.theme.ResourceColor import com.cmpe451.resq.viewmodels.ProfileViewModel -@Composable -fun ProfileButton(color: Color, text:String, route: String, navController: NavController) { - Button( - onClick = { - if (route.isNotEmpty()){ - navController.navigate(route) - } - - }, - colors = ButtonDefaults.buttonColors(color), - modifier = Modifier - .size(170.dp, 60.dp) - ) { - Text(text = text) - } -} - -@Composable -fun ProfilePhoto() { - var imageUri = rememberSaveable { mutableStateOf("") } - val launcher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.GetContent() - ) { uri: Uri? -> - uri?.let { imageUri.value = it.toString() } - } - if (imageUri.value.isEmpty()) { - Image( - Icons.Default.AccountCircle, - contentDescription = "User Profile", - modifier = Modifier - .size(150.dp) - .clickable { launcher.launch("image/*") }, - ) - } - else{ - val painter = rememberAsyncImagePainter(imageUri.value) - Image( - painter = painter, - contentDescription = "User Profile", - - modifier = Modifier - .size(150.dp) - .clip(CircleShape) - .clickable { launcher.launch("image/*") }, - contentScale = ContentScale.Crop - ) - } - // TO DO: Save imageUri.value to database, and retrieve it when the user logs in again. - // TO DO: Add deletion option -} +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.text.input.KeyboardType +import com.cmpe451.resq.data.manager.UserSessionManager import java.time.Year import androidx.compose.material.MaterialTheme.typography @@ -206,11 +149,11 @@ fun ProfileScreen(navController: NavController, appContext: Context) { Text("Loading...") } else -> { - val userRoles = UserSessionManager.getInstance(appContext).getUserRoles() + val userRoles = profileData!!.roles if (userRoles != null) { if (userRoles.contains("VICTIM") || userRoles.contains("RESPONDER") || userRoles.contains("FACILITATOR")) { - Profile(profileData = profileData!!, navController = navController, viewModel, appContext) + Profile(profileData = profileData!!, navController = navController, availableRoles, viewModel, appContext) } else { Text("Unknown Role") @@ -224,7 +167,7 @@ private fun String.isDigit() = filter { it.isDigit() } fun generateYears(start: Int, end: Int): List{ val years = mutableListOf() - for (i in end downTo start){ + for (i in start..end){ years.add(i.toString()) } return years @@ -255,83 +198,49 @@ fun generateDays(month: String): List{ @RequiresApi(Build.VERSION_CODES.O) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun Profile(profileData:ProfileData, navController: NavController, viewModel: ProfileViewModel, appContext: Context) { +fun Profile(profileData:ProfileData, navController: NavController, availableRoles: List, viewModel: ProfileViewModel, appContext: Context) { val genders = listOf("Male", "Female") val bloodTypes = listOf("AB Rh+", "AB Rh-", "A Rh+", "A Rh-", "B Rh+", "B Rh-", "O Rh+", "O Rh-") - val userSessionManager = UserSessionManager.getInstance(appContext) + val years = generateYears(1900, Year.now().value) + val months = generateMonths() + var selectedRole = { mutableStateOf(profileData.selectedRole ?: "") } var name by remember { mutableStateOf(profileData.name ?: "") } var surname by remember { mutableStateOf(profileData.surname ?: "") } - var weight by remember { mutableStateOf(profileData.weight?.toString() ?: "") } + var year by remember { mutableStateOf(profileData.year ?: "") } + var month by remember { mutableStateOf(profileData.month ?: "") } + var day by remember { mutableStateOf(profileData.day ?: "") } + var email by remember { mutableStateOf(profileData.email ?: "") } + var weight by remember { mutableStateOf(profileData.weight ?: "") } var gender by remember { mutableStateOf(profileData.gender ?: "") } - var height by remember { mutableStateOf(profileData.height?.toString() ?: "") } + var height by remember { mutableStateOf(profileData.height ?: "") } var country by remember { mutableStateOf(profileData.country ?: "") } var city by remember { mutableStateOf(profileData.city ?: "") } var state by remember { mutableStateOf(profileData.state ?: "") } var phoneNumber by remember { mutableStateOf(profileData.phoneNumber ?: "") } var bloodType by remember { mutableStateOf(profileData.bloodType ?: "") } - var isPhoneValid by remember { mutableStateOf(false) } + var isEmailValid by remember { mutableStateOf(false) } + var isPhoneValid by remember { mutableStateOf(false) } var message by remember { mutableStateOf("") } val snackbarHostState = remember { SnackbarHostState() } - var profileColor = Color(0xFFFFFFFF) - - when (userSessionManager.getSelectedRole()) { - "VICTIM" -> { - profileColor = RequestColor - } - - "RESPONDER" -> { - profileColor = ResourceColor - } - - "FACILITATOR" -> { - profileColor = MyTasksColor - } - } - - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .background(Color.White) - ) { - TopAppBar( - navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { - Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back") + val coroutineScope = rememberCoroutineScope() + val modalBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) + + ModalBottomSheetLayout( + sheetContent = { + BottomSheetContent( + availableRoles = availableRoles, + onRoleSelected = { selectedRole -> + viewModel.selectRole(selectedRole, appContext) + // Handle the role selection + coroutineScope.launch { modalBottomSheetState.hide() } } - }, - title = { - Text( - text = "Account", - style = TextStyle( - fontSize = 25.sp, - color = Color(0xFF224957), - textAlign = TextAlign.Center - ), - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - }, - modifier = Modifier.fillMaxWidth() - ) - Row(verticalAlignment = Alignment.CenterVertically) { - Column( - modifier = Modifier - .weight(1f) - .padding(start = 16.dp) - ) { - ProfilePhoto() - } - Text( - text = "$name $surname", - modifier = Modifier.weight(1f), - style = TextStyle( - fontWeight = FontWeight.Bold, - fontSize = 18.sp - ) ) - } - Box( + }, + sheetState = modalBottomSheetState, + + ) { + Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) @@ -393,184 +302,239 @@ fun Profile(profileData:ProfileData, navController: NavController, viewModel: Pr .fillMaxWidth() .padding(16.dp) ) { - OutlinedTextField( - value = name, - onValueChange = { name = it.letterOrSpace() }, - label = { Text("First Name") }, - shape = RoundedCornerShape(15), - modifier = Modifier - .weight(1f) - .padding(4.dp) - .background(Color.White), - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text - ), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black - ) - ) - - OutlinedTextField( - value = surname, - onValueChange = { surname = it.letterOrSpace() }, - label = { Text("Last Name") }, - shape = RoundedCornerShape(15), + Row( modifier = Modifier - .weight(1f) + .fillMaxWidth() .padding(4.dp) - .background(Color.White), - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text - ), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black - ) - ) - } + .background(Color.White) + ) { + name?.let { + OutlinedTextField( + value = it, + onValueChange = { name = it.letterOrSpace() }, + label = { Text("First Name") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text + ), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { + ) - isPhoneValid = android.util.Patterns.PHONE.matcher(phoneNumber).matches() - OutlinedTextField( - value = phoneNumber, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Phone - ), - onValueChange = { - phoneNumber = it.isDigit() - isPhoneValid = android.util.Patterns.PHONE.matcher(it).matches() - }, + ) + } - label = { Text("Phone Number") }, - shape = RoundedCornerShape(15), + surname?.let { + OutlinedTextField( + value = it, + onValueChange = { surname = it.letterOrSpace() }, + label = { Text("Last Name") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text + ), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black + ) + ) + } + + } + + Row( modifier = Modifier - .weight(1f) - .padding(4.dp) - .background(Color.White), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black, - focusedBorderColor = if (isPhoneValid) Color.Black else Color.Red - ) - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - OutlinedTextField( - value = country, - onValueChange = { country = it.letterOrSpace() }, - label = { Text("Country") }, - shape = RoundedCornerShape(15), - modifier = Modifier - .weight(1f) + .fillMaxWidth() .padding(4.dp) - .background(Color.White), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black, + ) { + email?.let { + val isValidEmail = + android.util.Patterns.EMAIL_ADDRESS.matcher(it).matches() + isEmailValid = isValidEmail + + OutlinedTextField( + value = it, + onValueChange = { + email = it + isEmailValid = + android.util.Patterns.EMAIL_ADDRESS.matcher(it) + .matches() + }, + label = { Text("Email") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black, + focusedBorderColor = if (isEmailValid) Color.Black else Color.Red + ), + singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email + ) ) - ) - OutlinedTextField( - value = city, - onValueChange = { city = it.letterOrSpace() }, - label = { Text("City") }, - shape = RoundedCornerShape(15), - modifier = Modifier - .weight(1f) - .padding(4.dp) - .background(Color.White), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black, - ) - ) - OutlinedTextField( - value = state, - onValueChange = { state = it.letterOrSpace() }, - label = { Text("State") }, - shape = RoundedCornerShape(15), + } + } + Row( modifier = Modifier - .weight(1f) + .fillMaxWidth() .padding(4.dp) - .background(Color.White), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black, - ) - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - OutlinedTextField( - value = weight, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number - ), - onValueChange = { weight = it.isDigit() }, - label = { Text("Weight (kg)") }, - shape = RoundedCornerShape(15), + ) { + phoneNumber?.let { + OutlinedTextField( + value = it, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Phone + ), + onValueChange = { + phoneNumber = it.isDigit() + isPhoneValid = + android.util.Patterns.PHONE.matcher(it).matches() + }, + + label = { Text("Phone Number") }, + shape = RoundedCornerShape(15), + + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black, + focusedBorderColor = if (isPhoneValid) Color.Black else Color.Red + ) + ) + + } + } + Row( modifier = Modifier - .weight(1f) + .fillMaxWidth() .padding(4.dp) - .background(Color.White), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black, - ) - ) - OutlinedTextField( - value = height, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number - ), - onValueChange = { height = it.isDigit() }, - label = { Text("Height (cm)") }, - shape = RoundedCornerShape(15), + ) { + country?.let { + OutlinedTextField( + value = it, + onValueChange = { country = it.letterOrSpace() }, + label = { Text("Country") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black, + + ) + ) + } + city?.let { + OutlinedTextField( + value = it, + onValueChange = { city = it.letterOrSpace() }, + label = { Text("City") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black, + ) + ) + + } + state?.let { + OutlinedTextField( + value = it, + onValueChange = { state = it.letterOrSpace() }, + label = { Text("State") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black, + ) + ) + + + } + } + Row( modifier = Modifier - .weight(1f) + .fillMaxWidth() .padding(4.dp) - .background(Color.White), - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = Color.White, - textColor = Color.Black, - cursorColor = Color.Black, - ) - ) - } - Row( - modifier = Modifier - .fillMaxWidth(), - ) { - Column(modifier = Modifier.weight(1f)) { - TextListSelectionWithColorChange( - items = genders, - selectedItem = gender, - onItemSelected = { gender = it }, - label = "Gender", - color = profileColor - ) + + weight?.let { + OutlinedTextField( + value = it, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number + ), + onValueChange = { weight = it.isDigit() }, + label = { Text("Weight (kg)") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black + ) + ) + + } + height?.let { + OutlinedTextField( + value = it, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number + ), + onValueChange = { height = it.isDigit() }, + label = { Text("Height (cm)") }, + shape = RoundedCornerShape(15), + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + textColor = Color.Black, + cursorColor = Color.Black, + ) + ) + } } Row( modifier = Modifier @@ -589,83 +553,78 @@ fun Profile(profileData:ProfileData, navController: NavController, viewModel: Pr } } - Column(modifier = Modifier.weight(1f)) { - TextListSelectionWithColorChange( - items = bloodTypes, - selectedItem = bloodType, - onItemSelected = { bloodType = it }, - label = "Blood Type", - color = profileColor - ) + + Column(modifier = Modifier.weight(1f)) { + bloodType?.let { + TextListSelectionWithColorChange( + items = bloodTypes, + selectedItem = bloodType, + onItemSelected = { bloodType = it }, + label = "Blood Type", + color = Color(0xFFB356AF) + ) + } + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(4.dp) + ) { + Column(modifier = Modifier.weight(1f)) { + year?.let { + TextListSelectionWithColorChange( + items = years, + selectedItem = year, + onItemSelected = { year = it }, + label = "Year", + color = Color(0xFFB356AF) + ) + } + } + Column(modifier = Modifier.weight(1f)) { + month?.let { + TextListSelectionWithColorChange( + items = months, + selectedItem = month, + onItemSelected = { month = it }, + label = "Month", + color = Color(0xFFB356AF) + ) + } + } + Column(modifier = Modifier.weight(1f)) { + val days = generateDays(month) + day?.let { + TextListSelectionWithColorChange( + items = days, + selectedItem = day, + onItemSelected = { day = it }, + label = "Day", + color = Color(0xFFB356AF) + ) + } + } } } } } - Spacer(modifier = Modifier.weight(1f)) - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - when (UserSessionManager.getInstance(appContext).getSelectedRole()) { - "VICTIM" -> { - VictimProfileButtons(navController = navController) - } - - "RESPONDER" -> { - ResponderProfileButtons(navController = navController) - } - - "FACILITATOR" -> { - FacilitatorProfileButtons(navController = navController) - } - } + Spacer(modifier = Modifier.weight(1f)) - Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier.align(Alignment.Start) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), ) { Row( modifier = Modifier.align(Alignment.Start) ) { Button( onClick = { - if (!isPhoneValid) { - message = "Please check your phone number." - - } else { - // @TO DO Handle Save Details button click - Log.d("Save Details", "Save Details") - viewModel.updateProfile(appContext, ProfileData( - name = name, - surname = surname, - bloodType = bloodType, - country = country, - city = city, - state = state, - gender = gender.takeIf { it.isNotEmpty() } , - height = height.takeIf { it.isNotEmpty() }?.toInt(), - weight = weight.takeIf { it.isNotEmpty() }?.toInt(), - phoneNumber = phoneNumber, - birthdate = null - )) - if (viewModel.updateMessage.value != null) { - message = "Details saved successfully." - } - - else if (viewModel.errorMessage.value != null) { - message = viewModel.errorMessage.value!! - } - else{ - message = "Details saved successfully." - } - - } - + // @TO DO: Handle button click }, colors = ButtonDefaults.buttonColors(Color(0xFFB356AF)), modifier = Modifier @@ -673,14 +632,12 @@ fun Profile(profileData:ProfileData, navController: NavController, viewModel: Pr ) { Text(text = "My Requests") } - - Spacer(modifier = Modifier.width(30.dp)) - Button( - onClick = { - //@ TO DO Handle button click - }, - colors = ButtonDefaults.buttonColors(Color(0xFF224957)), - + } + Spacer(modifier = Modifier.height(20.dp)) + Row( + modifier = Modifier.align(Alignment.Start) + ) { + Row( modifier = Modifier .fillMaxWidth() .padding(4.dp) @@ -742,99 +699,6 @@ fun Profile(profileData:ProfileData, navController: NavController, viewModel: Pr ) } - SnackbarHost( - hostState = snackbarHostState, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) -} - -@Composable -fun FacilitatorProfileButtons(navController: NavController) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - ProfileButton( - color = ResourceColor, - text = "My Resources", - route = "", - navController = navController - ) - Spacer(modifier = Modifier.width(30.dp)) - ProfileButton( - color = MyTasksColor, - text = "My Tasks", - route = "", - navController = navController - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - ProfileButton( - color = RequestColor, - text = "My Request", - route = "", - navController = navController - ) - Spacer(modifier = Modifier.width(30.dp)) - ProfileButton( - color = OngoingTasksColor, - text = "Ongoing Tasks", - route = "OngoingTasks", - navController = navController - ) - } -} - - -@Composable -fun ResponderProfileButtons(navController: NavController) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - ProfileButton( - color = ResourceColor, - text = "My Resources", - route = "", - navController = navController - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - ProfileButton( - color = MyTasksColor, - text = "My Tasks", - route = "", - navController = navController - ) - } -} - -@Composable -fun VictimProfileButtons(navController: NavController) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - ProfileButton( - color = RequestColor, - text = "My Request", - route = "", - navController = navController - ) - } } @Composable diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ProfileViewModel.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ProfileViewModel.kt index 2d8eba50..3144fd73 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ProfileViewModel.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ProfileViewModel.kt @@ -1,11 +1,8 @@ package com.cmpe451.resq.viewmodels import android.content.Context -import android.os.Build import android.util.Log -import androidx.annotation.RequiresApi import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -18,14 +15,8 @@ import kotlinx.coroutines.launch class ProfileViewModel() : ViewModel() { private var _profileData: MutableState = mutableStateOf(null) val profile get() = _profileData - private val _errorMessage = MutableStateFlow(null) - val errorMessage = _errorMessage + private val errorMessage = MutableStateFlow(null) - private val _updateMessage = mutableStateOf(null) - val updateMessage: State = _updateMessage - - - @RequiresApi(Build.VERSION_CODES.O) fun getUserData(appContext: Context) { val api = ResqService(appContext) @@ -39,24 +30,16 @@ class ProfileViewModel() : ViewModel() { } } - - @RequiresApi(Build.VERSION_CODES.O) - fun updateProfile(appContext: Context, profileData: ProfileData){ + fun selectRole(role: String, appContext: Context) { + val userSessionManager: UserSessionManager = UserSessionManager.getInstance(appContext) val api = ResqService(appContext) + val roles = userSessionManager.getUserRoles() viewModelScope.launch { try { - val response = api.updateUserData(profileData) - - if (response.isSuccessful) { - _updateMessage.value = response.body() - _errorMessage.value = null - } - else { - _errorMessage.value = response.message() - ?: "Profile update failed. Please try again." - } + val response = api.selectRole(role) + Log.d("ProfileViewModel", "selectRole: $response") } catch (e: Exception) { - _errorMessage.value = e.message ?: "Unexpected error occurred." + errorMessage.value = e.message } } }