Compare commits

..

4 commits

5 changed files with 101 additions and 4 deletions

View file

@ -2,13 +2,16 @@ package com.p_vacho.neat_calendar.activities
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.p_vacho.neat_calendar.MyApplication
@ -32,6 +35,8 @@ import retrofit2.HttpException
class NotificationsActivity : AppCompatActivity() {
private lateinit var rvNotifications: RecyclerView
private lateinit var btnBack: ImageButton
private lateinit var tvEmptyState: TextView
private lateinit var tvSwipeHint: TextView
private lateinit var notifications: MutableList<NotificationResponse>
private lateinit var invitations: MutableMap<String, InvitationResponse> // invitation id -> invitation
@ -51,6 +56,8 @@ class NotificationsActivity : AppCompatActivity() {
rvNotifications = findViewById(R.id.rvNotifications)
btnBack = findViewById(R.id.btnBack)
tvEmptyState = findViewById(R.id.tvEmptyState)
tvSwipeHint = findViewById(R.id.tvSwipeHint)
btnBack.setOnClickListener { finish() }
@ -66,7 +73,7 @@ class NotificationsActivity : AppCompatActivity() {
invitations = invitationsDeferred.await().toMutableMap()
events = eventsDeferred.await().toMutableMap()
rvNotifications.adapter = NotificationAdapter(
val adapter = NotificationAdapter(
notifications,
::handleNotificationAction,
::handleNotificationClick,
@ -74,6 +81,10 @@ class NotificationsActivity : AppCompatActivity() {
::getUserData,
::getEventData,
)
rvNotifications.adapter = adapter
setupSwipeToDelete(adapter)
updateEmptyState()
}
}
@ -139,7 +150,7 @@ class NotificationsActivity : AppCompatActivity() {
val ret = invitations[invitationId]
if (ret == null) {
Log.w("NotificationsActivity", "NotificationAdapter requested unknown invitation: $invitationId")
Log.w("NotificationsActivity", "Known invitations: $invitations")
Log.w("NotificationsActivity", "Known invitations (${invitations.size}): $invitations")
}
return ret
}
@ -148,7 +159,7 @@ class NotificationsActivity : AppCompatActivity() {
val ret = events[eventId]
if (ret == null) {
Log.w("NotificationsActivity", "NotificationAdapter requested unknown event: $eventId")
Log.w("NotificationsActivity", "Known events: $events")
Log.w("NotificationsActivity", "Known events (${events.size}): $events")
}
return ret
}
@ -250,4 +261,49 @@ class NotificationsActivity : AppCompatActivity() {
}
}
}
private fun setupSwipeToDelete(adapter: NotificationAdapter) {
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false // We don't support moving items
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
val notification = notifications[position]
// Call the deletion method
lifecycleScope.launch(Dispatchers.IO) {
RetrofitClient.notificationsService.deleteNotification(notification.id)
withContext(Dispatchers.Main) {
// Remove the notification & notify the adapter about it
notifications.removeAt(position)
adapter.notifyItemRemoved(position)
// Annoyingly, we can't just use notifyItemRemoved for the single removed item,
// as all the items below it would now be using the wrong position that was
// already bounded to the callbacks from the click listeners, so we need to refresh
// all of the notifications below this one as well.
adapter.notifyItemRangeChanged(position, notifications.size - position)
Toast.makeText(this@NotificationsActivity, "Notification deleted", Toast.LENGTH_SHORT).show()
updateEmptyState()
}
}
}
})
// Attach ItemTouchHelper to the RecyclerView
itemTouchHelper.attachToRecyclerView(rvNotifications)
}
private fun updateEmptyState() {
val isEmpty = notifications.isEmpty()
tvEmptyState.visibility = if (isEmpty) View.VISIBLE else View.GONE
tvSwipeHint.visibility = if (isEmpty) View.GONE else View.VISIBLE
}
}

View file

@ -32,7 +32,7 @@ interface EventsService {
@Query("start_to") startTo: OffsetDateTime? = null,
@Query("end_from") endFrom: OffsetDateTime? = null,
@Query("end_to") endTo: OffsetDateTime? = null,
@Query("inviteStatus") inviteStatus: String? = null, // "pending" / "accepted" / null (both)
@Query("invite_status") inviteStatus: String? = null, // "pending" / "accepted" / null (both)
): List<EventResponse>
@DELETE("events/{event_id}")

View file

@ -1,6 +1,7 @@
package com.p_vacho.neat_calendar.api.services
import com.p_vacho.neat_calendar.api.models.NotificationResponse
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
@ -14,4 +15,10 @@ interface NotificationService {
@POST("notifications/{notification_id}/read")
suspend fun markNotificationRead(@Path("notification_id") notificationId: String): NotificationResponse
@POST("notifications/{notification_id}/unread")
suspend fun markNotificationUnread(@Path("notification_id") notificationId: String): NotificationResponse
@DELETE("notifications/{notification_id}")
suspend fun deleteNotification(@Path("notification_id") notificationId: String): Unit
}

View file

@ -62,4 +62,36 @@
android:layout_marginBottom="16dp"
android:layout_marginTop="8dp" />
<!-- Swipe Hint -->
<TextView
android:id="@+id/tvSwipeHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/swipe_to_delete_hint"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary"
android:gravity="center"
android:padding="16dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/rvNotifications"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Empty State -->
<TextView
android:id="@+id/tvEmptyState"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/no_notifications"
android:textSize="16sp"
android:textColor="?android:attr/textColorSecondary"
android:gravity="center"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/titleBar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -79,4 +79,6 @@
<string name="decline">Decline</string>
<string name="notifications">Notifications</string>
<string name="view_event">View Event</string>
<string name="swipe_to_delete_hint">Swipe right on a notification to delete it.</string>
<string name="no_notifications">You\'re all caught up! No notifications right now.</string>
</resources>