diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/MainActivity.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/MainActivity.kt index fc298f50..1391a0be 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/MainActivity.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/MainActivity.kt @@ -97,7 +97,7 @@ fun NavGraph( RequestScreen(navController, appContext) } composable(NavigationItem.Resource.route) { - ResourceScreen(navController) + ResourceScreen(navController, appContext) } composable(NavigationItem.OngoingTasks.route) { OngoingTasksScreen(navController) diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/CategoryTreeNode.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/CategoryTreeNode.kt new file mode 100644 index 00000000..c5959c4d --- /dev/null +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/CategoryTreeNode.kt @@ -0,0 +1,7 @@ +package com.cmpe451.resq.data.models + +data class CategoryTreeNode( + val id: Int, + val data: String, + val children: List +) \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Request.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Request.kt index 577a257f..b52aa85b 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Request.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Request.kt @@ -1,10 +1,5 @@ package com.cmpe451.resq.data.models -data class CategoryNode( - val id: Int, - val data: String, - val children: List -) data class CreateNeedRequestBody( val description: String, diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Resource.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Resource.kt new file mode 100644 index 00000000..9fff75c7 --- /dev/null +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/Resource.kt @@ -0,0 +1,10 @@ +package com.cmpe451.resq.data.models + +data class CreateResourceRequestBody( + var senderId: Int?, + val categoryTreeId: String, + val quantity: Int, + val latitude: Double, + val longitude: Double, + val gender: String +) 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 fab038f9..4885a326 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 @@ -5,8 +5,9 @@ import android.os.Build import androidx.annotation.RequiresApi import com.cmpe451.resq.data.Constants import com.cmpe451.resq.data.manager.UserSessionManager -import com.cmpe451.resq.data.models.CategoryNode +import com.cmpe451.resq.data.models.CategoryTreeNode import com.cmpe451.resq.data.models.CreateNeedRequestBody +import com.cmpe451.resq.data.models.CreateResourceRequestBody import com.cmpe451.resq.data.models.LoginRequestBody import com.cmpe451.resq.data.models.LoginResponse import com.cmpe451.resq.data.models.ProfileData @@ -26,9 +27,17 @@ interface CategoryTreeNodeService { suspend fun getMainCategories( @Header("Authorization") jwtToken: String, @Header("X-Selected-Role") role: String - ): Response> + ): Response> } +interface ResourceService { + @POST("resource/createResource") + suspend fun createResource( + @Header("Authorization") jwtToken: String, + @Header("X-Selected-Role") role: String, + @Body requestBody: CreateResourceRequestBody + ): Response +} interface NeedService { @POST("need/createNeed") suspend fun createNeed( @@ -74,6 +83,7 @@ class ResqService(appContext: Context) { .build() private val categoryTreeNodeService: CategoryTreeNodeService = retrofit.create(CategoryTreeNodeService::class.java) + private val resourceService: ResourceService = retrofit.create(ResourceService::class.java) private val needService: NeedService = retrofit.create(NeedService::class.java) private val authService: AuthService = retrofit.create(AuthService::class.java) private val profileService: ProfileService = retrofit.create(ProfileService::class.java) @@ -81,7 +91,7 @@ class ResqService(appContext: Context) { private val userSessionManager: UserSessionManager = UserSessionManager.getInstance(appContext) // Category Tree Node methods - suspend fun getMainCategories(): Response> { + suspend fun getMainCategories(): Response> { val token = userSessionManager.getUserToken() ?: "" val selectedRole = userSessionManager.getSelectedRole() ?: "" @@ -91,16 +101,31 @@ class ResqService(appContext: Context) { ) } + // Resource methods + suspend fun createResource(request: CreateResourceRequestBody): Response { + val userId = userSessionManager.getUserId() + val token = userSessionManager.getUserToken() ?: "" + // val selectedRole = userSessionManager.getSelectedRole() ?: "" + + request.senderId = userId + + return resourceService.createResource( + jwtToken = "Bearer $token", + role = "RESPONDER", + requestBody = request + ) + } + // Need methods suspend fun createNeed(request: CreateNeedRequestBody): Response { val userId = userSessionManager.getUserId() val token = userSessionManager.getUserToken() ?: "" - val selectedRole = userSessionManager.getSelectedRole() ?: "" + // val selectedRole = userSessionManager.getSelectedRole() ?: "" return needService.createNeed( userId = userId, jwtToken = "Bearer $token", - role = selectedRole, + role = "VICTIM", requestBody = request ) } diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/components/DropdownMenuComponent.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/components/DropdownMenuComponent.kt new file mode 100644 index 00000000..466e7cfb --- /dev/null +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/components/DropdownMenuComponent.kt @@ -0,0 +1,68 @@ +package com.cmpe451.resq.ui.views.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.Icon +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun DropdownMenuComponent( + label: String, + items: List, + selectedItem: T, + itemToString: (T) -> String, + onItemSelected: (T) -> Unit +) { + var expandState by remember { mutableStateOf(false) } + Box(modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.Start) + ) { + OutlinedTextField( + value = itemToString(selectedItem), + onValueChange = {}, + label = { Text(label) }, + readOnly = true, + modifier = Modifier + .fillMaxWidth() + .clickable { expandState = true }, + trailingIcon = { + Icon( + imageVector = if (expandState) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, + contentDescription = "Dropdown Icon", + modifier = Modifier.clickable { expandState = !expandState } + ) + } + ) + DropdownMenu( + expanded = expandState, + onDismissRequest = { expandState = false }, + modifier = Modifier + .fillMaxWidth() + ) { + items.forEach { item -> + DropdownMenuItem(onClick = { + onItemSelected(item) + expandState = false + }) { + Text(text = itemToString(item)) + } + } + } + } +} \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/RequestScreen.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/RequestScreen.kt index 90619039..30376c90 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/RequestScreen.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/RequestScreen.kt @@ -2,7 +2,6 @@ package com.cmpe451.resq.ui.views.screens import android.content.Context import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,13 +9,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults -import androidx.compose.material.DropdownMenu -import androidx.compose.material.DropdownMenuItem import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.OutlinedTextField @@ -24,8 +20,6 @@ import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -42,9 +36,10 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import com.cmpe451.resq.data.models.CategoryNode +import com.cmpe451.resq.data.models.CategoryTreeNode import com.cmpe451.resq.ui.theme.LightGreen import com.cmpe451.resq.ui.theme.RequestColor +import com.cmpe451.resq.ui.views.components.DropdownMenuComponent import com.cmpe451.resq.viewmodels.RequestViewModel @Composable @@ -100,7 +95,7 @@ fun RequestScreen( DropdownMenuComponent( label = "Category", items = categories, - selectedItem = selectedCategoryState.value ?: CategoryNode(-1, "Select a Category", emptyList()), + selectedItem = selectedCategoryState.value ?: CategoryTreeNode(-1, "Select a Category", emptyList()), itemToString = { it.data }, onItemSelected = { category -> viewModel.updateCategory(category) @@ -112,7 +107,7 @@ fun RequestScreen( DropdownMenuComponent( label = "Type", items = viewModel.types.value, - selectedItem = viewModel.selectedType.value ?: CategoryNode(-1, "Select a Type", emptyList()), + selectedItem = viewModel.selectedType.value ?: CategoryTreeNode(-1, "Select a Type", emptyList()), itemToString = { it.data }, onItemSelected = { type -> viewModel.updateType(type) @@ -125,7 +120,7 @@ fun RequestScreen( DropdownMenuComponent( label = "Item", items = viewModel.items.value, - selectedItem = viewModel.selectedItem.value ?: CategoryNode(-1, "Select an Item", emptyList()), + selectedItem = viewModel.selectedItem.value ?: CategoryTreeNode(-1, "Select an Item", emptyList()), itemToString = { it.data }, onItemSelected = { item -> viewModel.updateItem(item) @@ -176,50 +171,3 @@ fun RequestScreen( SnackbarHost(hostState = snackbarHostState) } } - -@Composable -fun DropdownMenuComponent( - label: String, - items: List, - selectedItem: T, - itemToString: (T) -> String, - onItemSelected: (T) -> Unit -) { - var expandState by remember { mutableStateOf(false) } - Box(modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.Start) - ) { - OutlinedTextField( - value = itemToString(selectedItem), - onValueChange = {}, - label = { Text(label) }, - readOnly = true, - modifier = Modifier - .fillMaxWidth() - .clickable { expandState = true }, - trailingIcon = { - Icon( - imageVector = if (expandState) Icons.Filled.KeyboardArrowUp else Icons.Filled.ArrowDropDown, - contentDescription = "Dropdown Icon", - modifier = Modifier.clickable { expandState = !expandState } - ) - } - ) - DropdownMenu( - expanded = expandState, - onDismissRequest = { expandState = false }, - modifier = Modifier - .fillMaxWidth() - ) { - items.forEach { item -> - DropdownMenuItem(onClick = { - onItemSelected(item) - expandState = false - }) { - Text(text = itemToString(item)) - } - } - } - } -} \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ResourceScreen.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ResourceScreen.kt index 3745181e..8b913999 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ResourceScreen.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/ResourceScreen.kt @@ -1,7 +1,7 @@ package com.cmpe451.resq.ui.views.screens +import android.content.Context import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -13,8 +13,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults -import androidx.compose.material.DropdownMenu -import androidx.compose.material.DropdownMenuItem import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.OutlinedTextField @@ -22,8 +20,11 @@ import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -35,88 +36,39 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import com.cmpe451.resq.data.models.CategoryTreeNode import com.cmpe451.resq.ui.theme.LightGreen import com.cmpe451.resq.ui.theme.ResourceColor +import com.cmpe451.resq.ui.views.components.DropdownMenuComponent import com.cmpe451.resq.viewmodels.ResourceViewModel -@Composable -fun DropdownMenuComponentWithColorChange( - label: String, - items: List, - selectedItem: String, - expanded: Boolean, - onItemSelected: (String) -> Unit -) { - var expanded by remember { mutableStateOf(expanded) } - var selected by remember { mutableStateOf(selectedItem) } - - Box( - modifier = Modifier - .fillMaxWidth() - .clickable { expanded = true } - ) { - OutlinedTextField( - value = selectedItem, - onValueChange = {}, - label = { Text(label) }, - readOnly = true, - modifier = Modifier.fillMaxWidth(), - trailingIcon = { - Icon( - imageVector = Icons.Default.ArrowDropDown, - contentDescription = "Dropdown Icon", - modifier = Modifier.clickable { expanded = !expanded } - ) - } - ) - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - items.forEach { item -> - DropdownMenuItem( - onClick = { - selected = item - onItemSelected(item) - expanded = false - }, - modifier = Modifier.background(if (item == selected) ResourceColor else Color.Transparent) - ) { - Text(text = item, color = if (item == selected) Color.White else Color.Black) - } - } - } - } -} - @Composable fun ResourceScreen( navController: NavController, + appContext: Context ) { val viewModel: ResourceViewModel = viewModel() - // Variables for dropdown menu - var typeExpanded by remember { mutableStateOf(false) } - val types = listOf("Human Resource", "Clothes", "Food", "Medicine") - var selectedType by remember { mutableStateOf(types[0]) } - var professionExpanded by remember { mutableStateOf(false) } - val professions = listOf("Truck Driver", "Medic", "Crane Operator") - var selectedProfession by remember { mutableStateOf(professions[0]) } + LaunchedEffect(key1 = true) { + viewModel.fetchMainCategories(appContext) + } + + val selectedCategoryState = viewModel.selectedCategory + val categories = viewModel.categories.value var quantity by remember { mutableStateOf("") } + + val snackbarHostState = remember { SnackbarHostState() } + Column( modifier = Modifier .background(Color.White) .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { + // Top Bar with back button and title TopAppBar( - title = { Text(text = "Resource", - color = ResourceColor) }, - modifier = Modifier - .fillMaxWidth() - .align(Alignment.CenterHorizontally), + title = { Text(text = "Resource", color = ResourceColor) }, navigationIcon = { IconButton(onClick = { navController.navigateUp() }) { Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back") @@ -136,34 +88,49 @@ fun ResourceScreen( modifier = Modifier .fillMaxWidth() .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally) { - DropdownMenuComponentWithColorChange( - label = "Type", - items = types, - selectedItem = selectedType, - expanded = false - ) { - selectedType = it - typeExpanded = false - } + horizontalAlignment = Alignment.CenterHorizontally + ) { + + DropdownMenuComponent( + label = "Category", + items = categories, + selectedItem = selectedCategoryState.value ?: CategoryTreeNode(-1, "Select a Category", emptyList()), + itemToString = { it.data }, + onItemSelected = { category -> + viewModel.updateCategory(category) + } + ) Spacer(modifier = Modifier.height(16.dp)) - if (selectedType == "Human Resource") { - DropdownMenuComponentWithColorChange( - label = "Profession", - items = professions, - selectedItem = selectedProfession, - expanded = false - ) { selectedProfession = it } + DropdownMenuComponent( + label = "Type", + items = viewModel.types.value, + selectedItem = viewModel.selectedType.value ?: CategoryTreeNode(-1, "Select a Type", emptyList()), + itemToString = { it.data }, + onItemSelected = { type -> + viewModel.updateType(type) + viewModel.fetchItemsForType(type.id) + } + ) - Spacer(modifier = Modifier.height(16.dp)) - } + Spacer(modifier = Modifier.height(16.dp)) + DropdownMenuComponent( + label = "Item", + items = viewModel.items.value, + selectedItem = viewModel.selectedItem.value ?: CategoryTreeNode(-1, "Select an Item", emptyList()), + itemToString = { it.data }, + onItemSelected = { item -> + viewModel.updateItem(item) + } + ) + + Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = quantity, // you can bind this to a state variable to store the quantity - onValueChange = { quantity= it }, + value = quantity, + onValueChange = { quantity = it }, label = { Text("Quantity") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth() @@ -173,7 +140,7 @@ fun ResourceScreen( // Enter Button Button( - onClick = { viewModel.onEnter() }, + onClick = { viewModel.onEnter(quantity, appContext) }, colors = ButtonDefaults.buttonColors(backgroundColor = LightGreen), shape = RoundedCornerShape(50) ) { @@ -181,8 +148,16 @@ fun ResourceScreen( } } } + // Success and Error messages + if (viewModel.createResourceResponse.value != null) { + LaunchedEffect(key1 = viewModel.createResourceResponse.value) { + snackbarHostState.showSnackbar( + message = "Resource created successfully.", + duration = SnackbarDuration.Long + ) + } + } + SnackbarHost(hostState = snackbarHostState) } } - - diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/RequestViewModel.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/RequestViewModel.kt index 9b7307c6..6d6d4b2d 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/RequestViewModel.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/RequestViewModel.kt @@ -5,47 +5,44 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.cmpe451.resq.data.models.CategoryNode +import com.cmpe451.resq.data.models.CategoryTreeNode import com.cmpe451.resq.data.models.CreateNeedRequestBody -import com.cmpe451.resq.data.models.LoginRequestBody -import com.cmpe451.resq.data.models.LoginResponse import com.cmpe451.resq.data.remote.ResqService -import com.cmpe451.resq.utils.NavigationItem import kotlinx.coroutines.launch class RequestViewModel : ViewModel() { - private val _selectedCategory = mutableStateOf(null) - val selectedCategory: State = _selectedCategory + private val _selectedCategory = mutableStateOf(null) + val selectedCategory: State = _selectedCategory - private val _selectedType = mutableStateOf(null) - val selectedType: State = _selectedType + private val _selectedType = mutableStateOf(null) + val selectedType: State = _selectedType - private val _selectedItem = mutableStateOf(null) - val selectedItem: State = _selectedItem + private val _selectedItem = mutableStateOf(null) + val selectedItem: State = _selectedItem - private val _categories = mutableStateOf>(emptyList()) - val categories: State> = _categories + private val _categories = mutableStateOf>(emptyList()) + val categories: State> = _categories - private val _types = mutableStateOf>(emptyList()) - val types: State> = _types + private val _types = mutableStateOf>(emptyList()) + val types: State> = _types - private val _items = mutableStateOf>(emptyList()) - val items: State> = _items + private val _items = mutableStateOf>(emptyList()) + val items: State> = _items private val _createNeedResponse = mutableStateOf(null) val createNeedResponse: State = _createNeedResponse - fun updateCategory(category: CategoryNode) { + fun updateCategory(category: CategoryTreeNode) { _selectedCategory.value = category fetchTypesForCategory(category.id) } - fun updateType(type: CategoryNode) { + fun updateType(type: CategoryTreeNode) { _selectedType.value = type fetchItemsForType(type.id) } - fun updateItem(item: CategoryNode) { + fun updateItem(item: CategoryTreeNode) { _selectedItem.value = item } diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ResourceViewModel.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ResourceViewModel.kt index 0dc8e4c4..37b34fea 100644 --- a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ResourceViewModel.kt +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/ResourceViewModel.kt @@ -1,33 +1,112 @@ package com.cmpe451.resq.viewmodels +import android.content.Context import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.cmpe451.resq.data.models.CategoryTreeNode +import com.cmpe451.resq.data.models.CreateResourceRequestBody +import com.cmpe451.resq.data.remote.ResqService +import kotlinx.coroutines.launch class ResourceViewModel : ViewModel() { + private val _selectedCategory = mutableStateOf(null) + val selectedCategory: State = _selectedCategory - private val _selectedType = mutableStateOf("Food") - val selectedType: State = _selectedType + private val _selectedType = mutableStateOf(null) + val selectedType: State = _selectedType - private val _selectedProfesion = mutableStateOf("Truck Driver") - val selectedProfession: State = _selectedProfesion + private val _selectedItem = mutableStateOf(null) + val selectedItem: State = _selectedItem - private val _quantity = mutableStateOf(null) - val quantity: State = _quantity - // Update functions - fun updateType(type: String) { + private val _categories = mutableStateOf>(emptyList()) + val categories: State> = _categories + + private val _types = mutableStateOf>(emptyList()) + val types: State> = _types + + private val _items = mutableStateOf>(emptyList()) + val items: State> = _items + + private val _createResourceResponse = mutableStateOf(null) + val createResourceResponse: State = _createResourceResponse + + fun updateCategory(category: CategoryTreeNode) { + _selectedCategory.value = category + fetchTypesForCategory(category.id) + } + + fun updateType(type: CategoryTreeNode) { _selectedType.value = type + fetchItemsForType(type.id) + } + + fun updateItem(item: CategoryTreeNode) { + _selectedItem.value = item } - fun updateProfession(profession: String) { - _selectedProfesion.value = profession + private fun fetchTypesForCategory(categoryId: Int) { + _types.value = _categories.value.find { it.id == categoryId }?.children ?: emptyList() + _selectedType.value = null + _selectedItem.value = null } - fun updateQuantity(quantity: String) { - _quantity.value = quantity + fun fetchItemsForType(typeId: Int) { + val selectedCategoryChildren = _selectedCategory.value?.children ?: emptyList() + _items.value = selectedCategoryChildren.find { it.id == typeId }?.children ?: emptyList() + _selectedItem.value = null } - // Handle the enter action - fun onEnter() { + fun fetchMainCategories(appContext: Context) { + viewModelScope.launch { + val api = ResqService(appContext) + + val response = api.getMainCategories() + if (response.isSuccessful) { + _categories.value = response.body() ?: emptyList() + if (_categories.value.isNotEmpty()) { + _selectedCategory.value = _categories.value.first() + } + } else { + // TODO: Handle error + } + } + } + + fun onEnter(quantity: String, appContext: Context) { + viewModelScope.launch { + val result = getCreateResourceResponse(quantity, appContext) + + if (result.isSuccess) { + _createResourceResponse.value = result.getOrNull().toString() + } else { + _createResourceResponse.value = result.exceptionOrNull()?.message + } + } + } + + private suspend fun getCreateResourceResponse(quantity: String, appContext: Context): Result { + val api = ResqService(appContext) + val categoryId = _selectedItem.value?.id?.toString() ?: _selectedType.value?.id?.toString() ?: "" + + if (categoryId.isNotEmpty()) { + val requestBody = CreateResourceRequestBody( + senderId = null, + categoryTreeId = categoryId, + quantity = quantity.toIntOrNull() ?: 0, + latitude = 0.0, + longitude = 0.0, + gender = "FEMALE" + ) + val response = api.createResource(requestBody) + if (response.isSuccessful) { + response.body()?.let { + return Result.success(it) + } + } + return Result.failure(Throwable(response.message())) + } + return Result.failure(Throwable(message = "No category")) } } diff --git a/resq/mobile/ResQ/secrets.properties b/resq/mobile/ResQ/secrets.properties new file mode 100644 index 00000000..4fc93b7d --- /dev/null +++ b/resq/mobile/ResQ/secrets.properties @@ -0,0 +1 @@ +MAPS_API_KEY=AIzaSyANcTSGzEKneY4AGgvYs720ZNUgiTUIDb0 \ No newline at end of file