feat(notifications): Fetch corresponding invitations

This commit is contained in:
Peter Vacho 2025-01-04 13:15:34 +01:00
parent ac36100c74
commit 046da599b7
Signed by: school
GPG key ID: 8CFC3837052871B4
4 changed files with 91 additions and 14 deletions

View file

@ -14,6 +14,7 @@ import com.p_vacho.neat_calendar.MyApplication
import com.p_vacho.neat_calendar.R import com.p_vacho.neat_calendar.R
import com.p_vacho.neat_calendar.adapters.NotificationAdapter import com.p_vacho.neat_calendar.adapters.NotificationAdapter
import com.p_vacho.neat_calendar.api.RetrofitClient import com.p_vacho.neat_calendar.api.RetrofitClient
import com.p_vacho.neat_calendar.api.models.InvitationResponse
import com.p_vacho.neat_calendar.api.models.NotificationResponse import com.p_vacho.neat_calendar.api.models.NotificationResponse
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -24,6 +25,7 @@ class NotificationsActivity : AppCompatActivity() {
private lateinit var btnBack: ImageButton private lateinit var btnBack: ImageButton
private lateinit var notifications: MutableList<NotificationResponse> private lateinit var notifications: MutableList<NotificationResponse>
private lateinit var invitations: MutableMap<String, InvitationResponse> // invitation id -> invitation
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -43,7 +45,10 @@ class NotificationsActivity : AppCompatActivity() {
lifecycleScope.launch { lifecycleScope.launch {
notifications = fetchNotifications().toMutableList() notifications = fetchNotifications().toMutableList()
rvNotifications.adapter = NotificationAdapter(notifications, ::handleNotificationAction, ::handleNotificationClick) invitations = fetchInvitations().toMutableMap()
rvNotifications.adapter = NotificationAdapter(notifications, ::handleNotificationAction, ::handleNotificationClick) {
notification -> invitations[notification.data]
}
} }
btnBack.setOnClickListener { finish() } btnBack.setOnClickListener { finish() }
@ -64,6 +69,19 @@ class NotificationsActivity : AppCompatActivity() {
return notifications return notifications
} }
private suspend fun fetchInvitations(): Map<String, InvitationResponse> {
val userId = (application as MyApplication).tokenManager.userId
if (userId == null) {
finish()
return emptyMap()
}
val fetchedInvitations = withContext(Dispatchers.IO) {
RetrofitClient.invitationService.getIncomingInvitations(userId)
}
return fetchedInvitations.associateBy { it.id }
}
private fun handleNotificationAction(notification: NotificationResponse, action: NotificationAdapter.Action, position: Int) { private fun handleNotificationAction(notification: NotificationResponse, action: NotificationAdapter.Action, position: Int) {
when (action) { when (action) {
NotificationAdapter.Action.ACCEPT -> { NotificationAdapter.Action.ACCEPT -> {

View file

@ -3,11 +3,11 @@ package com.p_vacho.neat_calendar.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.p_vacho.neat_calendar.R import com.p_vacho.neat_calendar.R
import com.p_vacho.neat_calendar.api.models.InvitationResponse
import com.p_vacho.neat_calendar.api.models.NotificationResponse import com.p_vacho.neat_calendar.api.models.NotificationResponse
import java.time.Duration import java.time.Duration
import java.time.OffsetDateTime import java.time.OffsetDateTime
@ -17,6 +17,7 @@ class NotificationAdapter(
private val notifications: MutableList<NotificationResponse>, private val notifications: MutableList<NotificationResponse>,
private val onActionClick: (NotificationResponse, Action, Int) -> Unit, private val onActionClick: (NotificationResponse, Action, Int) -> Unit,
private val onNotificationClick: (NotificationResponse, Int) -> Unit, private val onNotificationClick: (NotificationResponse, Int) -> Unit,
private val getInvitationData: (NotificationResponse) -> InvitationResponse?,
) : RecyclerView.Adapter<NotificationAdapter.NotificationViewHolder>() { ) : RecyclerView.Adapter<NotificationAdapter.NotificationViewHolder>() {
enum class Action { enum class Action {
@ -42,8 +43,6 @@ class NotificationAdapter(
override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) { override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) {
val notification = notifications[position] val notification = notifications[position]
holder.message.text = notification.message
// Format and set the creation time // Format and set the creation time
val formattedTime = formatNotificationTime(notification.created_at) val formattedTime = formatNotificationTime(notification.created_at)
holder.notificationTime.text = formattedTime holder.notificationTime.text = formattedTime
@ -53,7 +52,21 @@ class NotificationAdapter(
// Handle invitation actions // Handle invitation actions
if (notification.event_type == "invitation") { if (notification.event_type == "invitation") {
val invitation = getInvitationData(notification)
// TODO: Consider fetching the user names here to show
// TODO: Localize
if (invitation != null) {
if (notification.message == "new-invitation") {
holder.message.setText("You have received an event invitation")
} else if (notification.message == "invitation-accepted") {
holder.message.setText("Your event invitation has been accepted")
} else if (notification.message == "invitation-declined") {
holder.message.setText("Your event invitation has been declined")
}
holder.invitationActions.visibility = View.VISIBLE holder.invitationActions.visibility = View.VISIBLE
holder.acceptButton.setOnClickListener { holder.acceptButton.setOnClickListener {
onActionClick(notification, Action.ACCEPT, position) onActionClick(notification, Action.ACCEPT, position)
} }
@ -63,7 +76,22 @@ class NotificationAdapter(
holder.viewEventButton.setOnClickListener { holder.viewEventButton.setOnClickListener {
onActionClick(notification, Action.VIEW_EVENT, position) onActionClick(notification, Action.VIEW_EVENT, position)
} }
} else { } else {
if (notification.message == "new-invitation") {
holder.message.setText("You have received an event invitation [invitation deleted]")
} else if (notification.message == "invitation-accepted") {
holder.message.setText("Your event invitation has been accepted [invitation deleted]")
} else if (notification.message == "invitation-declined") {
holder.message.setText("Your event invitation has been declined [invitation deleted]")
}
holder.invitationActions.visibility = View.GONE
}
} else {
holder.message.text = notification.message
holder.invitationActions.visibility = View.GONE holder.invitationActions.visibility = View.GONE
} }

View file

@ -1,6 +1,18 @@
package com.p_vacho.neat_calendar.api.models package com.p_vacho.neat_calendar.api.models
import java.time.OffsetDateTime
data class InvitationRequest( data class InvitationRequest(
val event_id: String, val event_id: String,
val invitee_id: String, val invitee_id: String,
) )
data class InvitationResponse(
val id: String,
val invitor_id: String,
val invitee_id: String,
val event_id: String,
val status: String, // "accepted" / "declined" / "pending"
val sent_at: OffsetDateTime,
val responded_at: OffsetDateTime?,
)

View file

@ -1,10 +1,29 @@
package com.p_vacho.neat_calendar.api.services package com.p_vacho.neat_calendar.api.services
import com.p_vacho.neat_calendar.api.models.InvitationRequest import com.p_vacho.neat_calendar.api.models.InvitationRequest
import com.p_vacho.neat_calendar.api.models.InvitationResponse
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path
interface InvitationsService { interface InvitationsService {
@POST("invitations") @POST("invitations")
suspend fun createInvitation(@Body invitationData: InvitationRequest) suspend fun createInvitation(@Body invitationData: InvitationRequest): InvitationResponse
@GET("invitations/{invitation_id}")
suspend fun getInvitation(@Path("invitation_id") invitiationId: String): InvitationResponse
@GET("users/{user_id}/invitations")
suspend fun getInvitations(@Path("user_id") userId: String): List<InvitationResponse>
@GET("users/{user_id}/invitations/incoming")
suspend fun getIncomingInvitations(@Path("user_id") userId: String): List<InvitationResponse>
@POST("invitations/{invitation_id}/accept")
suspend fun acceptInvitation(@Path("invitation_id") invitationId: String): Unit
@POST("invitations/{invitation_id}/decline")
suspend fun declineInvitation(@Path("invitation_id") invitationId: String): Unit
} }