From c23cf24988c0f6df4673f9e30bed3b7ff8797bf7 Mon Sep 17 00:00:00 2001 From: Ilgaz Er Date: Tue, 28 Nov 2023 00:46:50 +0300 Subject: [PATCH 1/2] small fixes --- resq/frontend/src/components/Resource/ResourceAddress.js | 4 ++-- resq/frontend/src/components/Resource/ResourceContext.js | 2 +- resq/frontend/src/pages/ResourceCreation.js | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/resq/frontend/src/components/Resource/ResourceAddress.js b/resq/frontend/src/components/Resource/ResourceAddress.js index af704eb2..b2ce6943 100644 --- a/resq/frontend/src/components/Resource/ResourceAddress.js +++ b/resq/frontend/src/components/Resource/ResourceAddress.js @@ -39,7 +39,7 @@ export default function CreateResourceForm() { latitude: parseFloat(latitudeValue), }); }; - +/* const handleChange = (event) => { const { name, value } = event.target; setResource(prevState => ({ @@ -51,7 +51,7 @@ export default function CreateResourceForm() { const handleSubmit = (event) => { event.preventDefault(); console.log(resource); - }; + };*/ return ( diff --git a/resq/frontend/src/components/Resource/ResourceContext.js b/resq/frontend/src/components/Resource/ResourceContext.js index 616d1815..77cc19e4 100644 --- a/resq/frontend/src/components/Resource/ResourceContext.js +++ b/resq/frontend/src/components/Resource/ResourceContext.js @@ -1,6 +1,6 @@ import React, { createContext, useState, useContext } from 'react'; -const ResourceContext = createContext(); +export const ResourceContext = createContext({}); export const useResource = () => useContext(ResourceContext); diff --git a/resq/frontend/src/pages/ResourceCreation.js b/resq/frontend/src/pages/ResourceCreation.js index 17b3da52..4510b069 100644 --- a/resq/frontend/src/pages/ResourceCreation.js +++ b/resq/frontend/src/pages/ResourceCreation.js @@ -15,8 +15,7 @@ import ResourceAddress from '../components/Resource/ResourceAddress'; import ResourceDetail1 from '../components/Resource/ResourceDetail1'; import ResourceDetail2 from '../components/Resource/ResourceDetail2'; import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { ResourceProvider } from '../components/Resource/ResourceContext'; -import { useResource } from './ResourceContext'; +import {ResourceProvider, useResource} from '../components/Resource/ResourceContext'; import axios from 'axios'; From 7e6e74304289a6b925c78ea6ff6e5554987d0876 Mon Sep 17 00:00:00 2001 From: alperenDagi <111731140+alperenDagi@users.noreply.github.com> Date: Tue, 28 Nov 2023 01:07:41 +0300 Subject: [PATCH 2/2] Resolve conflict on user profile --- resq/mobile/ResQ/app/build.gradle.kts | 1 + .../com/cmpe451/resq/data/models/Profile.kt | 37 +- .../cmpe451/resq/data/remote/ResqService.kt | 61 +- .../java/com/cmpe451/resq/ui/theme/Color.kt | 2 + .../resq/ui/views/screens/ProfileScreen.kt | 677 +++++++++--------- .../resq/viewmodels/ProfileViewModel.kt | 37 +- 6 files changed, 443 insertions(+), 372 deletions(-) diff --git a/resq/mobile/ResQ/app/build.gradle.kts b/resq/mobile/ResQ/app/build.gradle.kts index 4ab1bd7c..27384fdd 100644 --- a/resq/mobile/ResQ/app/build.gradle.kts +++ b/resq/mobile/ResQ/app/build.gradle.kts @@ -101,6 +101,7 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") + implementation("io.coil-kt:coil-compose:2.4.0") } secrets{ 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 939051bd..bd81f655 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,25 +3,32 @@ package com.cmpe451.resq.data.models data class ProfileData( var name: String?, var surname: String?, - var email: String?, - var roles: List?, - var selectedRole: String?, + var birthdate: String?, + var gender:String?, + var bloodType: String?, var phoneNumber: String?, var country: String?, var city: String?, var state: String?, - var bloodType: String?, - var weight: String?, - var gender:String?, - var height: String?, - var year: String?, - var month: String?, - var day: String? + var weight: Int?, + var height: Int?, + val emailConfirmed: Boolean? = false, + val privacyPolicyAccepted: Boolean? = false, ) -data class UserInfoResponse( - val name: String, - val surname: String, - val email: String, - val roles: List + +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, ) \ 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 33aefaf5..bceb198c 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,6 @@ 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 @@ -69,12 +68,12 @@ interface AuthService { } interface ProfileService { - @GET("user/getUserInfo") + @GET("profile/getProfileInfo") 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( @@ -91,7 +90,7 @@ interface ProfileService { @Header("Authorization") jwtToken: String, @Header("X-Selected-Role") role: String, @Body request: UserInfoRequest - ): Response + ): Response } @@ -176,7 +175,7 @@ class ResqService(appContext: Context) { return null } - try { + return try { val date = LocalDate.parse(birthDate) val year = date.year.toString() val month = date.monthValue.toString() @@ -185,7 +184,7 @@ class ResqService(appContext: Context) { return Triple(year, month, day) } catch (e: DateTimeParseException) { //TO DO Handle parsing error if needed - return null + null } } @@ -202,53 +201,47 @@ class ResqService(appContext: Context) { role = selectedRole ) - 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( + 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(), ) - 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 ?: "", - email = profileData.email ?: "", - roles = profileData.roles ?: listOf(), - birth_date = formattedBirthDate, + birthdate = null, country = profileData.country ?: "", city = profileData.city ?: "", state = profileData.state ?: "", bloodType = profileData.bloodType ?: "", - height = profileData.height?.toIntOrNull(), - weight = profileData.weight?.toIntOrNull(), + height = profileData.height, + weight = profileData.weight, gender = profileData.gender ?: "", phoneNumber = profileData.phoneNumber ?: "", ) - val response = profileService.updateProfile( + return profileService.updateProfile( userId = userId, jwtToken = "Bearer $token", role = selectedRole, request = request ) - return response - - - } suspend fun selectRole(requestedRole: String): Response { @@ -265,4 +258,4 @@ class ResqService(appContext: Context) { return response } -} +} \ No newline at end of file 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 4043aa33..72df4920 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,4 +16,6 @@ val LightGreen = Color(0xff20df7f) val ResourceColor = Color(0xFF397FE7) val RequestColor = Color(0xFFB356AF) +val MyTasksColor = Color(0XFFE7A139) +val OngoingTasksColor = Color(0xFFE16834) 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 b04adc24..84660f1e 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,6 +1,12 @@ +@file:Suppress("DEPRECATION") + package com.cmpe451.resq.ui.views.screens + import android.content.Context +import android.net.Uri import android.os.Build +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 @@ -17,18 +23,11 @@ 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 -import androidx.compose.material.Divider -import androidx.compose.material.DropdownMenu -import androidx.compose.material.DropdownMenuItem -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.ModalBottomSheetLayout -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.TextButton +import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.ArrowBack @@ -49,10 +48,14 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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 @@ -61,20 +64,16 @@ 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 -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 import kotlinx.coroutines.launch -import androidx.compose.material.* + @OptIn(ExperimentalMaterial3Api::class) @Composable fun TextListSelectionWithColorChange( @@ -129,6 +128,56 @@ fun TextListSelectionWithColorChange( } } +@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 +} @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @RequiresApi(Build.VERSION_CODES.O) @@ -149,7 +198,7 @@ fun ProfileScreen(navController: NavController, appContext: Context) { Text("Loading...") } else -> { - val userRoles = profileData!!.roles + val userRoles = UserSessionManager.getInstance(appContext).getUserRoles() if (userRoles != null) { if (userRoles.contains("VICTIM") || userRoles.contains("RESPONDER") || userRoles.contains("FACILITATOR")) { @@ -201,31 +250,27 @@ fun generateDays(month: String): List{ 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 years = generateYears(1900, Year.now().value) - val months = generateMonths() - var selectedRole = { mutableStateOf(profileData.selectedRole ?: "") } + val userSessionManager = UserSessionManager.getInstance(appContext) var name by remember { mutableStateOf(profileData.name ?: "") } var surname by remember { mutableStateOf(profileData.surname ?: "") } - 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 weight by remember { mutableStateOf(profileData.weight?.toString() ?: "") } var gender by remember { mutableStateOf(profileData.gender ?: "") } - var height by remember { mutableStateOf(profileData.height ?: "") } + var height by remember { mutableStateOf(profileData.height?.toString() ?: "") } 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 isEmailValid by remember { mutableStateOf(false) } - var isPhoneValid by remember { mutableStateOf(false) } + var isPhoneValid by remember { mutableStateOf(false) } var message by remember { mutableStateOf("") } val snackbarHostState = remember { SnackbarHostState() } + var profileColor = Color(0xFFFFFFFF) + val coroutineScope = rememberCoroutineScope() val modalBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) + ModalBottomSheetLayout( sheetContent = { BottomSheetContent( @@ -266,13 +311,13 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole modifier = Modifier.fillMaxWidth() ) Row(verticalAlignment = Alignment.CenterVertically) { - Image( - Icons.Default.AccountCircle, - contentDescription = "User Profile", + Column( modifier = Modifier - .size(150.dp) .weight(1f) - ) + .padding(start = 16.dp) + ) { + ProfilePhoto() + } Text( text = "$name $surname", modifier = Modifier.weight(1f), @@ -282,8 +327,6 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole ) ) } - - Box( modifier = Modifier .fillMaxSize() @@ -308,233 +351,170 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole .padding(4.dp) .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 - - ) - + 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, + focusedTextColor = Color.Black, + cursorColor = Color.Black ) - } - - - 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 - ) + ) + + OutlinedTextField( + value = surname, + 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, + focusedTextColor = Color.Black, + cursorColor = Color.Black ) - } - + ) } Row( modifier = Modifier .fillMaxWidth() .padding(4.dp) - ) { - 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 - ) + + 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), + + modifier = Modifier + .weight(1f) + .padding(4.dp) + .background(Color.White), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = Color.White, + focusedTextColor = Color.Black, + cursorColor = Color.Black, + focusedBorderColor = if (isPhoneValid) Color.Black else Color.Red ) - } + ) } Row( modifier = Modifier .fillMaxWidth() .padding(4.dp) ) { - 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 - ) - ) + OutlinedTextField( + value = country, + 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, + focusedTextColor = Color.Black, + cursorColor = Color.Black, - } - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - 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, ) + ) + 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, + focusedTextColor = 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, - ) + ) + OutlinedTextField( + value = state, + 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, + focusedTextColor = Color.Black, + cursorColor = Color.Black, ) - - - } + ) } Row( modifier = Modifier .fillMaxWidth() .padding(4.dp) ) { - - 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 - ) + OutlinedTextField( + value = weight, + 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, + focusedTextColor = 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, - ) + ) + OutlinedTextField( + value = height, + 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, + focusedTextColor = Color.Black, + cursorColor = Color.Black, ) - } + ) } Row( modifier = Modifier @@ -542,75 +522,29 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole ) { Column(modifier = Modifier.weight(1f)) { - gender?.let { - TextListSelectionWithColorChange( - items = genders, - selectedItem = gender, - onItemSelected = { gender = it }, - label = "Gender", - color = Color(0xFFB356AF) - ) - } + TextListSelectionWithColorChange( + items = genders, + selectedItem = gender, + onItemSelected = { gender = it }, + label = "Gender", + 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) - ) - } + TextListSelectionWithColorChange( + items = bloodTypes, + selectedItem = bloodType, + onItemSelected = { bloodType = it }, + label = "Blood Type", + color = profileColor + ) } } } } } - Spacer(modifier = Modifier.weight(1f)) Column( @@ -619,20 +553,21 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole .padding(start = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Row( - modifier = Modifier.align(Alignment.Start) - ) { - Button( - onClick = { - // @TO DO: Handle button click - }, - colors = ButtonDefaults.buttonColors(Color(0xFFB356AF)), - modifier = Modifier - .size(170.dp, 60.dp) - ) { - Text(text = "My Requests") + + when (UserSessionManager.getInstance(appContext).getSelectedRole()) { + "VICTIM" -> { + VictimProfileButtons(navController = navController) + } + + "RESPONDER" -> { + ResponderProfileButtons(navController = navController) + } + + "FACILITATOR" -> { + FacilitatorProfileButtons(navController = navController) } } + Spacer(modifier = Modifier.height(20.dp)) Row( modifier = Modifier.align(Alignment.Start) @@ -644,19 +579,35 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole ) { Button( onClick = { - - if (!isEmailValid and !isPhoneValid) { - // @TO DO: Save details - message = "Please check your email address and phone number." - - } else if (!isPhoneValid) { + if (!isPhoneValid) { message = "Please check your phone number." - } else if (!isEmailValid) { - message = "Please check your email address." } else { // @TO DO Handle Save Details button click - message = "Details saved successfully." + 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." + } + } }, @@ -666,7 +617,7 @@ fun Profile(profileData:ProfileData, navController: NavController, availableRole Text(text = "Save Details") } - Spacer(modifier = Modifier.width(25.dp)) + Spacer(modifier = Modifier.width(30.dp)) Button( onClick = { //@ TO DO Handle button click @@ -719,4 +670,90 @@ fun BottomSheetContent(availableRoles: List, onRoleSelected: (String) -> Divider() } } +} + +@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 + ) + } } \ No newline at end of file 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 3144fd73..5aeed2f4 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,8 +1,11 @@ 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 @@ -15,14 +18,21 @@ import kotlinx.coroutines.launch class ProfileViewModel() : ViewModel() { private var _profileData: MutableState = mutableStateOf(null) val profile get() = _profileData - private val errorMessage = MutableStateFlow(null) + private val _errorMessage = MutableStateFlow(null) + val errorMessage = _errorMessage + private val _updateMessage = mutableStateOf(null) + val updateMessage: State = _updateMessage + + + @RequiresApi(Build.VERSION_CODES.O) fun getUserData(appContext: Context) { val api = ResqService(appContext) viewModelScope.launch { try { val data = api.getUserInfo() + Log.d("Service", "getUserData: $data") _profileData.value = data } catch (e: Exception) { errorMessage.value = e.message @@ -30,6 +40,28 @@ class ProfileViewModel() : ViewModel() { } } + + @RequiresApi(Build.VERSION_CODES.O) + fun updateProfile(appContext: Context, profileData: ProfileData){ + val api = ResqService(appContext) + viewModelScope.launch { + try { + Log.d("Update DATA", "") + 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." + } + } catch (e: Exception) { + _errorMessage.value = e.message ?: "Unexpected error occurred." + } + } + } fun selectRole(role: String, appContext: Context) { val userSessionManager: UserSessionManager = UserSessionManager.getInstance(appContext) val api = ResqService(appContext) @@ -43,5 +75,4 @@ class ProfileViewModel() : ViewModel() { } } } - -} +} \ No newline at end of file