Skip to content

Commit

Permalink
Merge pull request #605 from bounswe/feature/mobile-546-my-requests-page
Browse files Browse the repository at this point in the history
My requests page added
  • Loading branch information
HarunErgen authored Dec 24, 2023
2 parents 9a5dbfc + 6842bfc commit 5ba8bcd
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import com.cmpe451.resq.utils.NavigationItem
import com.cmpe451.resq.viewmodels.MapViewModel
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices

import com.cmpe451.resq.ui.views.screens.MyRequestsScreen
class MainActivity : ComponentActivity() {

private val requestPermissionLauncher =
Expand Down Expand Up @@ -153,6 +153,9 @@ fun NavGraph(
composable(NavigationItem.Settings.route) {
SettingsScreen(navController, appContext)
}
composable(NavigationItem.MyRequestsScreen.route) {
MyRequestsScreen(navController, appContext)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ data class Need(
val longitude: Double,
val requestId: Int?,
val status: String,
val createdDate: String
)
val createdDate: String,
val size: String
)

Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import java.time.LocalDate
import java.time.format.DateTimeParseException


import retrofit2.Call
import retrofit2.Callback
interface CategoryTreeNodeService {
@GET("categorytreenode/getMainCategories")
suspend fun getMainCategories(
Expand Down Expand Up @@ -68,8 +68,13 @@ interface NeedService {
): Call<List<Need>>


}
@GET("need/viewNeedsByUserId")
fun viewNeedsByUserId(
@Query("userId") userId: Int,
@Header("Authorization") jwtToken: String,
): Call<List<Need>>

}
interface AuthService {
@POST("auth/signin")
suspend fun login(@Body requestBody: LoginRequestBody): Response<LoginResponse>
Expand Down Expand Up @@ -210,6 +215,27 @@ class ResqService(appContext: Context) {
})
}

fun viewNeedsByUserId(
onSuccess: (List<Need>) -> Unit,
onError: (Throwable) -> Unit
) {
val token = userSessionManager.getUserToken() ?: ""
val userId = userSessionManager.getUserId()
needService.viewNeedsByUserId(userId = userId, "Bearer $token").enqueue(object :
Callback<List<Need>> {
override fun onResponse(call: Call<List<Need>>, response: Response<List<Need>>) {
if (response.isSuccessful) {
response.body()?.let { onSuccess(it) }
} else {
onError(RuntimeException("Response not successful"))
}
}
override fun onFailure(call: Call<List<Need>>, t: Throwable) {
onError(t)
}
})
}

// Auth methods
suspend fun login(request: LoginRequestBody): Response<LoginResponse> = authService.login(request)
suspend fun register(request: RegisterRequestBody): Response<ResponseBody> = authService.register(request)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.cmpe451.resq.ui.views.screens
import android.content.Context
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
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.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.cmpe451.resq.data.models.Need
import com.cmpe451.resq.ui.theme.RequestColor
import com.cmpe451.resq.viewmodels.MyRequestsViewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import java.text.SimpleDateFormat
import java.util.Locale

fun convertToReadableDate(dateStr: String): String {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault())
val outputFormat = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault())

return try {
val date = inputFormat.parse(dateStr)
date?.let { outputFormat.format(it) } ?: "Unknown Date"
} catch (e: Exception) {
"Invalid Date"
}
}

@Composable
fun MyRequestsScreen(navController: NavController, appContext: Context) {
val viewModel: MyRequestsViewModel = viewModel()
val needs by viewModel.needs
val scrollState = rememberScrollState()
// A side effect to load the needs when the composable enters the composition
LaunchedEffect(key1 = true) {
viewModel.getNeeds(appContext)
}

Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "My Requests", color = RequestColor) },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
}
},
backgroundColor = Color.White,
elevation = 4.dp,
contentColor = Color.Black
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(16.dp))
needs.forEachIndexed { index, need ->
RequestCard(viewModel, requestNumber = index + 1, request = need)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}

@Composable
fun RequestCard(viewModel: MyRequestsViewModel,requestNumber: Int, request: Need) {
var isSelected by remember { mutableStateOf(false) }
val categoryName = viewModel.getCategoryName(request.categoryTreeId.toInt())

Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.clickable { isSelected = !isSelected },
elevation = if (isSelected) 4.dp else 0.dp,
shape = RoundedCornerShape(8.dp),
border = BorderStroke(width = if (isSelected) 2.dp else 0.dp, color = RequestColor)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(all = 16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "#$requestNumber",
style = MaterialTheme.typography.body1,
color = Color(0xFF007BFF)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = categoryName,
style = MaterialTheme.typography.body1,
color = RequestColor
)
}
if (isSelected) {
Text("Description: ${request.description}")
Text("Quantity: ${request.quantity}")
Text("Latitude: ${request.latitude}")
Text("Longitude: ${request.longitude}")
Text("Status: ${request.status}")
Text("Created Date: ${convertToReadableDate(request.createdDate)}")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -698,8 +698,8 @@ fun FacilitatorProfileButtons(navController: NavController) {
) {
ProfileButton(
color = RequestColor,
text = "My Request",
route = "request",
text = "My Requests",
route = "myRequests",
navController = navController
)
Spacer(modifier = Modifier.width(30.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum class NavigationItem(val route: String) {
Request(route = "request"),
Resource(route = "resource"),
Task(route = "task"),
MyRequestsScreen(route = "myRequests"),
OngoingTasks(route = "ongoingTasks");

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.cmpe451.resq.viewmodels

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.Need
import com.cmpe451.resq.data.remote.ResqService
import kotlinx.coroutines.launch
import android.content.Context

class MyRequestsViewModel : ViewModel() {
val needs = mutableStateOf<List<Need>>(emptyList())
private val _categoryTree = mutableStateOf<List<CategoryTreeNode>>(emptyList())
suspend fun getNeeds(appContext: Context) {
val api = ResqService(appContext)
api.viewNeedsByUserId(
onSuccess = { needList ->
needs.value = needList
fetchCategoryTree(appContext)
},
onError = { error ->
// Handle error
}
)
}

private fun fetchCategoryTree(context: Context) {
val api = ResqService(context)
viewModelScope.launch {
val response = api.getMainCategories() // Assuming this method exists and fetches the entire category tree
if (response.isSuccessful) {
_categoryTree.value = response.body() ?: emptyList()
} else {
// Handle error
}
}
}

fun getCategoryName(categoryId: Int): String {
return findCategoryName(_categoryTree.value, categoryId)
}

private fun findCategoryName(categoryList: List<CategoryTreeNode>, categoryId: Int): String {
for (category in categoryList) {
if (category.id == categoryId) return category.data
val foundName = findCategoryName(category.children, categoryId)
if (foundName.isNotEmpty() && foundName != "Unknown Category") return foundName
}
return "Unknown Category"
}
}

0 comments on commit 5ba8bcd

Please sign in to comment.