From 0149d8f44f3fc5bc20eafcb2257642793cae40f8 Mon Sep 17 00:00:00 2001
From: Taras Smakula <tarassmakula@gmail.com>
Date: Thu, 7 Mar 2024 17:14:19 +0200
Subject: [PATCH] Add loading indicators for invites

---
 .../feature/room/invites/InvitesViewModel.kt  | 61 +++++++++++++++++--
 .../room/invites/list/InvitesViewHolder.kt    | 15 +++--
 .../futo/circles/core/model/InviteListItem.kt |  7 ++-
 .../layout/list_item_connection_invite.xml    | 25 +++++++-
 .../res/layout/list_item_invited_circle.xml   | 23 +++++++
 .../res/layout/list_item_invited_gallery.xml  | 23 +++++++
 .../res/layout/list_item_invited_group.xml    | 23 +++++++
 .../res/layout/list_item_people_request.xml   | 23 +++++++
 8 files changed, 187 insertions(+), 13 deletions(-)

diff --git a/core/src/main/java/org/futo/circles/core/feature/room/invites/InvitesViewModel.kt b/core/src/main/java/org/futo/circles/core/feature/room/invites/InvitesViewModel.kt
index eafb2308f..aae308101 100644
--- a/core/src/main/java/org/futo/circles/core/feature/room/invites/InvitesViewModel.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/room/invites/InvitesViewModel.kt
@@ -1,5 +1,7 @@
 package org.futo.circles.core.feature.room.invites
 
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.asLiveData
@@ -11,7 +13,12 @@ import org.futo.circles.core.extensions.launchBg
 import org.futo.circles.core.feature.room.invite.ManageInviteRequestsDataSource
 import org.futo.circles.core.feature.workspace.SharedCircleDataSource
 import org.futo.circles.core.model.CircleRoomTypeArg
+import org.futo.circles.core.model.ConnectionInviteListItem
+import org.futo.circles.core.model.FollowRequestListItem
+import org.futo.circles.core.model.InviteHeader
+import org.futo.circles.core.model.InviteListItem
 import org.futo.circles.core.model.InviteTypeArg
+import org.futo.circles.core.model.RoomInviteListItem
 import javax.inject.Inject
 
 @HiltViewModel
@@ -25,29 +32,57 @@ class InvitesViewModel @Inject constructor(
     private val inviteType: InviteTypeArg = savedStateHandle.getOrThrow("type")
 
     val inviteResultLiveData = SingleEventLiveData<Response<Unit?>>()
-    val invitesLiveData = dataSource.getInvitesFlow(inviteType).asLiveData()
+    private val loadingItemsIdsList = MutableLiveData<Set<String>>(emptySet())
+    val invitesLiveData = MediatorLiveData<List<InviteListItem>>().also {
+        it.addSource(loadingItemsIdsList) { loadingItemsValue ->
+            val currentList = it.value ?: emptyList()
+            it.postValue(currentList.map { item ->
+                when (item) {
+                    is ConnectionInviteListItem -> item.copy(
+                        isLoading = loadingItemsValue.contains(item.id)
+                    )
+
+                    is FollowRequestListItem -> item.copy(
+                        isLoading = loadingItemsValue.contains(item.id)
+                    )
+
+                    is RoomInviteListItem -> item.copy(
+                        isLoading = loadingItemsValue.contains(item.id)
+                    )
+
+                    is InviteHeader -> item
+                }
+            })
+        }
+        it.addSource(dataSource.getInvitesFlow(inviteType).asLiveData()) { value ->
+            it.postValue(value)
+        }
+    }
 
     fun getInviteType() = inviteType
 
     fun rejectRoomInvite(roomId: String) {
         launchBg {
+            toggleItemLoading(roomId)
             val result = manageInviteRequestsDataSource.rejectInvite(roomId)
-            inviteResultLiveData.postValue(result)
+            postInviteResult(result, roomId)
         }
     }
 
     fun acceptRoomInvite(roomId: String, roomType: CircleRoomTypeArg) {
         launchBg {
+            toggleItemLoading(roomId)
             val result = manageInviteRequestsDataSource.acceptInvite(roomId, roomType)
-            inviteResultLiveData.postValue(result)
+            postInviteResult(result, roomId)
         }
     }
 
     fun onFollowRequestAnswered(userId: String, accepted: Boolean) {
         launchBg {
+            toggleItemLoading(userId)
             val result = if (accepted) dataSource.acceptFollowRequest(userId)
             else dataSource.declineFollowRequest(userId)
-            inviteResultLiveData.postValue(result)
+            postInviteResult(result, userId)
         }
     }
 
@@ -58,11 +93,25 @@ class InvitesViewModel @Inject constructor(
     fun onConnectionInviteAnswered(roomId: String, accepted: Boolean) {
         if (accepted)
             launchBg {
-               val result =  sharedCircleDataSource.acceptSharedCircleInvite(roomId)
-                inviteResultLiveData.postValue(result)
+                toggleItemLoading(roomId)
+                val result = sharedCircleDataSource.acceptSharedCircleInvite(roomId)
+                postInviteResult(result, roomId)
             }
         else rejectRoomInvite(roomId)
     }
 
+    private fun postInviteResult(result: Response<Unit?>, id: String) {
+        inviteResultLiveData.postValue(result)
+        toggleItemLoading(id)
+    }
+
+    private fun toggleItemLoading(id: String) {
+        val currentSet = loadingItemsIdsList.value?.toMutableSet() ?: return
+        val newLoadingSet = currentSet.apply {
+            if (this.contains(id)) remove(id)
+            else add(id)
+        }
+        loadingItemsIdsList.postValue(newLoadingSet)
+    }
 
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/feature/room/invites/list/InvitesViewHolder.kt b/core/src/main/java/org/futo/circles/core/feature/room/invites/list/InvitesViewHolder.kt
index ce4878c32..34acd22cc 100644
--- a/core/src/main/java/org/futo/circles/core/feature/room/invites/list/InvitesViewHolder.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/room/invites/list/InvitesViewHolder.kt
@@ -49,6 +49,7 @@ class InvitedGroupViewHolder(
         if (data !is RoomInviteListItem) return
 
         with(binding) {
+            lLoading.setIsVisible(data.isLoading)
             ivGroup.loadRoomProfileIcon(
                 data.info.avatarUrl,
                 data.info.title,
@@ -85,6 +86,7 @@ class InvitedCircleViewHolder(
         if (data !is RoomInviteListItem) return
 
         with(binding) {
+            lLoading.setIsVisible(data.isLoading)
             tvShowProfileImage.setIsVisible(data.shouldBlurIcon)
             ivCircle.loadRoomProfileIcon(
                 data.info.avatarUrl,
@@ -121,6 +123,7 @@ class InvitedGalleryViewHolder(
         if (data !is RoomInviteListItem) return
 
         with(binding) {
+            lLoading.setIsVisible(data.isLoading)
             tvGalleryTitle.text = data.info.title
             ivGallery.loadRoomProfileIcon(
                 data.info.avatarUrl,
@@ -148,8 +151,10 @@ class FollowRequestViewHolder(
     }
 
     override fun bind(data: InviteListItem) {
-        val user = (data as? FollowRequestListItem)?.user ?: return
-        bindUser(user)
+        if (data !is FollowRequestListItem) return
+
+        binding.lLoading.setIsVisible(data.isLoading)
+        bindUser(data.user)
         binding.tvReasonMessage.apply {
             setIsVisible(data.reasonMessage != null)
             text = data.reasonMessage
@@ -179,8 +184,10 @@ class ConnectionInviteViewHolder(
     }
 
     override fun bind(data: InviteListItem) {
-        val user = (data as? ConnectionInviteListItem)?.user ?: return
-        bindUser(user)
+        if (data !is ConnectionInviteListItem) return
+
+        binding.lLoading.setIsVisible(data.isLoading)
+        bindUser(data.user)
     }
 
     private fun bindUser(user: CirclesUserSummary) {
diff --git a/core/src/main/java/org/futo/circles/core/model/InviteListItem.kt b/core/src/main/java/org/futo/circles/core/model/InviteListItem.kt
index 7cd4a690a..be79cb77f 100644
--- a/core/src/main/java/org/futo/circles/core/model/InviteListItem.kt
+++ b/core/src/main/java/org/futo/circles/core/model/InviteListItem.kt
@@ -28,17 +28,20 @@ data class RoomInviteListItem(
     val info: RoomInfo,
     val isEncrypted: Boolean,
     val inviterName: String,
-    val shouldBlurIcon: Boolean
+    val shouldBlurIcon: Boolean,
+    val isLoading: Boolean = false
 ) : InviteListItem(roomId)
 
 data class FollowRequestListItem(
     val user: CirclesUserSummary,
-    val reasonMessage: String?
+    val reasonMessage: String?,
+    val isLoading: Boolean = false
 ) : InviteListItem(user.id)
 
 data class ConnectionInviteListItem(
     val roomId: String,
     val user: CirclesUserSummary,
+    val isLoading: Boolean = false
 ) : InviteListItem(roomId)
 
 fun RoomSummary.toRoomInviteListItem(roomType: CircleRoomTypeArg, shouldBlurIcon: Boolean) =
diff --git a/core/src/main/res/layout/list_item_connection_invite.xml b/core/src/main/res/layout/list_item_connection_invite.xml
index 1e9d9a371..893f0199f 100644
--- a/core/src/main/res/layout/list_item_connection_invite.xml
+++ b/core/src/main/res/layout/list_item_connection_invite.xml
@@ -47,7 +47,7 @@
         app:layout_constraintStart_toStartOf="@id/tvUserName"
         app:layout_constraintTop_toBottomOf="@id/tvUserName" />
 
-    
+
     <com.google.android.material.button.MaterialButton
         android:id="@+id/btnAccept"
         style="@style/AccentButtonStyle"
@@ -78,4 +78,27 @@
         app:layout_constraintStart_toEndOf="@id/btnAccept"
         app:layout_constraintTop_toTopOf="@id/btnAccept" />
 
+    <FrameLayout
+        android:id="@+id/lLoading"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:colorBackground"
+        android:clickable="true"
+        android:focusable="true"
+        android:outlineProvider="none"
+        android:translationZ="2dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivUserImage"
+        app:layout_constraintTop_toBottomOf="@+id/tvInvitesToConnect">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </FrameLayout>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/core/src/main/res/layout/list_item_invited_circle.xml b/core/src/main/res/layout/list_item_invited_circle.xml
index cc1d80790..ba08bb8ff 100644
--- a/core/src/main/res/layout/list_item_invited_circle.xml
+++ b/core/src/main/res/layout/list_item_invited_circle.xml
@@ -70,6 +70,7 @@
         app:layout_constraintTop_toBottomOf="@id/tvCircleTitle"
         tools:text="texsdt" />
 
+
     <com.google.android.material.button.MaterialButton
         android:id="@+id/btnAccept"
         style="@style/AccentButtonStyle"
@@ -96,5 +97,27 @@
         app:layout_constraintStart_toEndOf="@id/btnAccept"
         app:layout_constraintTop_toBottomOf="@+id/tvInvitedBy" />
 
+    <FrameLayout
+        android:id="@+id/lLoading"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:colorBackground"
+        android:clickable="true"
+        android:focusable="true"
+        android:outlineProvider="none"
+        android:translationZ="2dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivCircle"
+        app:layout_constraintTop_toBottomOf="@+id/tvInvitedBy">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </FrameLayout>
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/core/src/main/res/layout/list_item_invited_gallery.xml b/core/src/main/res/layout/list_item_invited_gallery.xml
index 93c89ce3a..e3491a4af 100644
--- a/core/src/main/res/layout/list_item_invited_gallery.xml
+++ b/core/src/main/res/layout/list_item_invited_gallery.xml
@@ -90,4 +90,27 @@
         app:layout_constraintStart_toEndOf="@id/btnAccept"
         app:layout_constraintTop_toBottomOf="@+id/tvInviterName" />
 
+    <FrameLayout
+        android:id="@+id/lLoading"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:colorBackground"
+        android:clickable="true"
+        android:focusable="true"
+        android:outlineProvider="none"
+        android:translationZ="2dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivGallery"
+        app:layout_constraintTop_toBottomOf="@+id/tvInviterName">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </FrameLayout>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/core/src/main/res/layout/list_item_invited_group.xml b/core/src/main/res/layout/list_item_invited_group.xml
index 4cf4a5d99..8b3e3f612 100644
--- a/core/src/main/res/layout/list_item_invited_group.xml
+++ b/core/src/main/res/layout/list_item_invited_group.xml
@@ -101,4 +101,27 @@
         app:layout_constraintStart_toEndOf="@id/btnAccept"
         app:layout_constraintTop_toBottomOf="@+id/tvInviterName" />
 
+    <FrameLayout
+        android:id="@+id/lLoading"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:colorBackground"
+        android:clickable="true"
+        android:focusable="true"
+        android:outlineProvider="none"
+        android:translationZ="2dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivGroup"
+        app:layout_constraintTop_toBottomOf="@+id/tvInviterName">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </FrameLayout>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/core/src/main/res/layout/list_item_people_request.xml b/core/src/main/res/layout/list_item_people_request.xml
index 7315a0483..a70971190 100644
--- a/core/src/main/res/layout/list_item_people_request.xml
+++ b/core/src/main/res/layout/list_item_people_request.xml
@@ -93,4 +93,27 @@
         app:layout_constraintStart_toEndOf="@id/btnAccept"
         app:layout_constraintTop_toTopOf="@id/btnAccept" />
 
+    <FrameLayout
+        android:id="@+id/lLoading"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:colorBackground"
+        android:clickable="true"
+        android:focusable="true"
+        android:outlineProvider="none"
+        android:translationZ="2dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivUserImage"
+        app:layout_constraintTop_toBottomOf="@+id/tvReasonMessage">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </FrameLayout>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
-- 
GitLab