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 e6d83495..7b42bc63 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 @@ -35,6 +35,7 @@ import com.cmpe451.resq.ui.theme.LightGreen import com.cmpe451.resq.ui.theme.ResQTheme import com.cmpe451.resq.ui.views.screens.LoginScreen import com.cmpe451.resq.ui.views.screens.MapScreen +import com.cmpe451.resq.ui.views.screens.NotificationScreen import com.cmpe451.resq.ui.views.screens.OngoingTasksScreen import com.cmpe451.resq.ui.views.screens.ProfileScreen import com.cmpe451.resq.ui.views.screens.RegistrationScreen @@ -143,7 +144,7 @@ fun NavGraph( ProfileScreen(navController, appContext) } composable(NavigationItem.Notifications.route) { - //NotificationsScreen(navController) + NotificationScreen(navController, appContext) } composable(NavigationItem.Settings.route) { SettingsScreen(navController, appContext) diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/NotificationItem.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/NotificationItem.kt new file mode 100644 index 00000000..38171279 --- /dev/null +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/data/models/NotificationItem.kt @@ -0,0 +1,13 @@ +package com.cmpe451.resq.data.models + +data class NotificationItem( + val id: Int, + val createdAt: String, + val modifiedAt: String, + val userId: Int, + val title: String, + val body: String, + val relatedEntityId: Int, + val notificationType: String, + val read: Boolean +) \ 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 c06aeff9..f77caa80 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 @@ -2,6 +2,7 @@ package com.cmpe451.resq.data.remote import android.content.Context import android.os.Build +import android.util.Log import androidx.annotation.RequiresApi import com.cmpe451.resq.data.Constants import com.cmpe451.resq.data.manager.UserSessionManager @@ -11,6 +12,7 @@ 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.Need +import com.cmpe451.resq.data.models.NotificationItem import com.cmpe451.resq.data.models.ProfileData import com.cmpe451.resq.data.models.RegisterRequestBody import com.cmpe451.resq.data.models.UserInfoRequest @@ -96,6 +98,14 @@ interface ProfileService { } +interface NotificationService { + @GET("notification/viewAllNotifications") + suspend fun getNotifications( + @Query("userId") userId: Int, + @Header("Authorization") jwtToken: String + ): Response> +} + class ResqService(appContext: Context) { var gson = GsonBuilder() @@ -112,6 +122,7 @@ class ResqService(appContext: Context) { 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) + private val notificationService: NotificationService = retrofit.create(NotificationService::class.java) private val userSessionManager: UserSessionManager = UserSessionManager.getInstance(appContext) @@ -267,4 +278,15 @@ class ResqService(appContext: Context) { return response } + + suspend fun getNotifications(): Response> { + val userId = userSessionManager.getUserId() + val token = userSessionManager.getUserToken() ?: "" + val response = notificationService.getNotifications( + userId = userId, + jwtToken = "Bearer $token", + ) + Log.d("AAA", "getNotifications: ${response.isSuccessful}") + return response + } } \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/NotificationScreen.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/NotificationScreen.kt new file mode 100644 index 00000000..15aa513f --- /dev/null +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/ui/views/screens/NotificationScreen.kt @@ -0,0 +1,123 @@ +package com.cmpe451.resq.ui.views.screens + +import android.annotation.SuppressLint +import android.content.Context +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Card +import androidx.compose.material.Icon +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.AccountCircle +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.cmpe451.resq.data.models.NotificationItem +import com.cmpe451.resq.viewmodels.NotificationViewModel + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@Composable +fun NotificationScreen(navController: NavController, appContext: Context) { + val viewModel = NotificationViewModel(appContext) + val notifications by viewModel.notificationItems + + Scaffold( + topBar = { + TopAppBar( + title = { Text("Notifications") }, + navigationIcon = { + Icon( + Icons.Default.Notifications, + contentDescription = "Notifications" + ) + }, + backgroundColor = Color.White, + contentColor = Color.Black + ) + } + ) { + NotificationList(notifications) + } +} + +@Composable +fun NotificationList(notificationItems: List) { + LazyColumn { + items(notificationItems) { item -> + NotificationItemCard(item) + } + } +} + +@Composable +fun NotificationItemCard(notification: NotificationItem) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + elevation = 2.dp + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + // Replace this with an actual image using Coil or other image loading library + Icon( + Icons.Default.AccountCircle, + contentDescription = "Profile", + modifier = Modifier.size(40.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Column { + Text( + text = notification.title, + fontWeight = FontWeight.Bold + ) + Text( + text = notification.createdAt, + style = MaterialTheme.typography.body2 + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = notification.body, + style = MaterialTheme.typography.body2 + ) + /* + if (notification.isActionable) { + Button(onClick = { /* TODO: Handle View action */ }) { + Text("VIEW") + } + } + */ + } + } + } +} \ No newline at end of file diff --git a/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/NotificationViewModel.kt b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/NotificationViewModel.kt new file mode 100644 index 00000000..5cbfec45 --- /dev/null +++ b/resq/mobile/ResQ/app/src/main/java/com/cmpe451/resq/viewmodels/NotificationViewModel.kt @@ -0,0 +1,69 @@ +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.NotificationItem +import com.cmpe451.resq.data.remote.ResqService +import kotlinx.coroutines.launch + +class NotificationViewModel(appContext: Context) : ViewModel() { + + private val _notificationItems by lazy { mutableStateOf>(listOf()) } + val notificationItems: State> = _notificationItems + + init { + fetchNotifications(appContext) + } + + private fun fetchNotifications(appContext: Context) { + viewModelScope.launch { + /* + val notifications = getNotifications(appContext) + notifications.getOrNull()?.let { + _notificationItems.value = it + } + */ + val notifications = + listOf( + NotificationItem( + id = 1, + createdAt = "2021-05-01T00:00:00.000Z", + modifiedAt = "2021-05-01T00:00:00.000Z", + userId = 1, + title = "Notification Title", + body = "Notification Body", + relatedEntityId = 1, + notificationType = "Notification Type", + read = false + ), + NotificationItem( + id = 2, + createdAt = "2021-05-01T00:00:00.000Z", + modifiedAt = "2021-05-01T00:00:00.000Z", + userId = 1, + title = "Notification Title", + body = "Notification Body", + relatedEntityId = 1, + notificationType = "Notification Type", + read = false + ), + ) + _notificationItems.value = notifications + + } + } + + suspend fun getNotifications(appContext: Context): Result> { + val api = ResqService(appContext) + val response = api.getNotifications() + if (response.isSuccessful) { + response.body()?.let { + return Result.success(it) + } + } + return Result.failure(Throwable(response.message())) + } +} \ No newline at end of file