From b1af01b9eacb02873aaff803864c8fa839a3f9be Mon Sep 17 00:00:00 2001
From: Taras Smakula <tarassmakula@gmail.com>
Date: Fri, 8 Sep 2023 17:26:41 +0300
Subject: [PATCH] Add invites and knoks to photos

---
 .../feature/circles/CirclesViewModel.kt       |  2 +-
 .../feature/circles/list/CirclesViewHolder.kt |  2 +-
 .../feature/groups/GroupsDataSource.kt        | 22 +-----
 .../feature/groups/list/GroupViewHolder.kt    |  5 +-
 .../circles/mapping/RoomSummaryMapping.kt     |  6 +-
 .../org/futo/circles/model/GroupListItem.kt   |  4 +-
 app/src/main/res/values/strings.xml           |  3 -
 auth/src/main/res/values/strings.xml          |  1 -
 .../core/mapping/RoomSummaryMapping.kt        | 20 ++++-
 .../circles/core/model/GalleryListItem.kt     | 27 ++++++-
 .../gallery/rooms/PickGalleryFragment.kt      |  6 +-
 .../gallery/rooms/PickGalleryViewModel.kt     |  7 +-
 .../gallery/rooms/list/GalleryViewHolder.kt   | 77 ++++++++++++++++--
 ...stAdapter.kt => PickGalleryListAdapter.kt} | 13 +--
 .../res/layout/list_item_invited_gallery.xml  | 79 +++++++++++++++++++
 ...llery.xml => list_item_joined_gallery.xml} |  0
 .../res/layout/list_item_request_gallery.xml  | 79 +++++++++++++++++++
 core/src/main/res/values/strings.xml          |  4 +
 .../gallery/feature/PhotosDataSource.kt       | 67 +++++++++++++---
 .../circles/gallery/feature/PhotosFragment.kt | 29 +++++--
 .../gallery/feature/PhotosListAdapter.kt      | 58 ++++++++++++++
 .../gallery/feature/PhotosViewModel.kt        | 38 ++++++++-
 .../res/layout/list_item_select_gallery.xml   |  2 +-
 23 files changed, 478 insertions(+), 73 deletions(-)
 rename core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/{PhotosListAdapter.kt => PickGalleryListAdapter.kt} (59%)
 create mode 100644 core/src/main/res/layout/list_item_invited_gallery.xml
 rename core/src/main/res/layout/{list_item_gallery.xml => list_item_joined_gallery.xml} (100%)
 create mode 100644 core/src/main/res/layout/list_item_request_gallery.xml
 create mode 100644 gallery/src/main/java/org/futo/circles/gallery/feature/PhotosListAdapter.kt

diff --git a/app/src/main/java/org/futo/circles/feature/circles/CirclesViewModel.kt b/app/src/main/java/org/futo/circles/feature/circles/CirclesViewModel.kt
index ace0a4f49..876ee23a9 100644
--- a/app/src/main/java/org/futo/circles/feature/circles/CirclesViewModel.kt
+++ b/app/src/main/java/org/futo/circles/feature/circles/CirclesViewModel.kt
@@ -12,7 +12,7 @@ import javax.inject.Inject
 
 @HiltViewModel
 class CirclesViewModel @Inject constructor(
-    private val dataSource: CirclesDataSource,
+    dataSource: CirclesDataSource,
     private val inviteRequestsDataSource: InviteRequestsDataSource
 ) : ViewModel() {
 
diff --git a/app/src/main/java/org/futo/circles/feature/circles/list/CirclesViewHolder.kt b/app/src/main/java/org/futo/circles/feature/circles/list/CirclesViewHolder.kt
index e21d9cb53..f87de8480 100644
--- a/app/src/main/java/org/futo/circles/feature/circles/list/CirclesViewHolder.kt
+++ b/app/src/main/java/org/futo/circles/feature/circles/list/CirclesViewHolder.kt
@@ -99,7 +99,7 @@ class InvitedCircleViewHolder(
             setIcon(ivCircle, data.info.avatarUrl, data.info.title)
             setTitle(tvCircleTitle, data.info.title)
             binding.tvInvitedBy.text =
-                context.getString(R.string.invited_by_format, data.inviterName)
+                context.getString(org.futo.circles.core.R.string.invited_by_format, data.inviterName)
         }
     }
 }
diff --git a/app/src/main/java/org/futo/circles/feature/groups/GroupsDataSource.kt b/app/src/main/java/org/futo/circles/feature/groups/GroupsDataSource.kt
index 03240110c..74c304469 100644
--- a/app/src/main/java/org/futo/circles/feature/groups/GroupsDataSource.kt
+++ b/app/src/main/java/org/futo/circles/feature/groups/GroupsDataSource.kt
@@ -5,11 +5,9 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.withContext
-import org.futo.circles.core.extensions.createResult
 import org.futo.circles.core.mapping.toRoomInfo
 import org.futo.circles.core.model.GROUP_TYPE
 import org.futo.circles.core.provider.MatrixSessionProvider
-import org.futo.circles.core.room.RoomRelationsBuilder
 import org.futo.circles.core.utils.UserUtils
 import org.futo.circles.mapping.toInviteGroupListItem
 import org.futo.circles.mapping.toJoinedGroupListItem
@@ -22,18 +20,12 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import javax.inject.Inject
 
-class GroupsDataSource @Inject constructor(
-    private val roomRelationsBuilder: RoomRelationsBuilder
-) {
-
-    val session by lazy {
-        MatrixSessionProvider.currentSession
-            ?: throw IllegalArgumentException("session is not created")
-    }
+class GroupsDataSource @Inject constructor() {
 
     fun getGroupsFlow() = combine(
-        session.roomService().getRoomSummariesLive(roomSummaryQueryParams()).asFlow(),
-        session.roomService().getChangeMembershipsLive().asFlow()
+        MatrixSessionProvider.getSessionOrThrow().roomService()
+            .getRoomSummariesLive(roomSummaryQueryParams()).asFlow(),
+        MatrixSessionProvider.getSessionOrThrow().roomService().getChangeMembershipsLive().asFlow()
     ) { roomSummaries, _ ->
         withContext(Dispatchers.IO) { filterGroups(roomSummaries) }
     }.distinctUntilChanged()
@@ -78,10 +70,4 @@ class GroupsDataSource @Inject constructor(
         }
         return requests
     }
-
-
-    suspend fun acceptInvite(roomId: String) = createResult {
-        MatrixSessionProvider.currentSession?.roomService()?.joinRoom(roomId)
-        roomRelationsBuilder.setInvitedGroupRelations(roomId)
-    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/feature/groups/list/GroupViewHolder.kt b/app/src/main/java/org/futo/circles/feature/groups/list/GroupViewHolder.kt
index 3c129a53b..5fdd949d2 100644
--- a/app/src/main/java/org/futo/circles/feature/groups/list/GroupViewHolder.kt
+++ b/app/src/main/java/org/futo/circles/feature/groups/list/GroupViewHolder.kt
@@ -118,7 +118,8 @@ class InvitedGroupViewHolder(
         setIcon(binding.ivGroup, data.info.avatarUrl, data.info.title)
         setIsEncrypted(binding.ivLock, data.isEncrypted)
         setTitle(binding.tvGroupTitle, data.info.title)
-        binding.tvInviterName.text = context.getString(R.string.invited_by_format, data.inviterName)
+        binding.tvInviterName.text =
+            context.getString(org.futo.circles.core.R.string.invited_by_format, data.inviterName)
     }
 }
 
@@ -142,7 +143,7 @@ class RequestGroupViewHolder(
         with(binding) {
             setIcon(ivGroup, data.info.avatarUrl, data.info.title)
             binding.tvRequestUserId.text = context.getString(
-                R.string.requested_to_join_format, data.requesterName
+                org.futo.circles.core.R.string.requested_to_join_format, data.requesterName
             )
             binding.tvRoomName.text = data.info.title
         }
diff --git a/app/src/main/java/org/futo/circles/mapping/RoomSummaryMapping.kt b/app/src/main/java/org/futo/circles/mapping/RoomSummaryMapping.kt
index 843961929..e74b5e692 100644
--- a/app/src/main/java/org/futo/circles/mapping/RoomSummaryMapping.kt
+++ b/app/src/main/java/org/futo/circles/mapping/RoomSummaryMapping.kt
@@ -1,6 +1,6 @@
 package org.futo.circles.mapping
 
-import org.futo.circles.core.extensions.notEmptyDisplayName
+import org.futo.circles.core.mapping.getInviterName
 import org.futo.circles.core.mapping.toRoomInfo
 import org.futo.circles.core.model.RoomInfo
 import org.futo.circles.core.provider.MatrixSessionProvider
@@ -11,7 +11,6 @@ import org.futo.circles.model.JoinedCircleListItem
 import org.futo.circles.model.JoinedGroupListItem
 import org.futo.circles.model.TimelineRoomListItem
 import org.matrix.android.sdk.api.session.getRoomSummary
-import org.matrix.android.sdk.api.session.getUserOrDefault
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
@@ -52,9 +51,6 @@ fun RoomSummary.toInviteCircleListItem() = InvitedCircleListItem(
 private fun RoomSummary.getFollowersCount(): Int =
     getTimelineRoomFor(roomId)?.roomSummary()?.otherMemberIds?.size ?: 0
 
-private fun RoomSummary.getInviterName() =
-    MatrixSessionProvider.currentSession?.getUserOrDefault(inviterId ?: "")?.notEmptyDisplayName()
-        ?: ""
 
 private fun RoomSummary.getCircleUnreadMessagesCount(): Int {
     var unreadInCircle = 0
diff --git a/app/src/main/java/org/futo/circles/model/GroupListItem.kt b/app/src/main/java/org/futo/circles/model/GroupListItem.kt
index 50471ba43..f41310a9b 100644
--- a/app/src/main/java/org/futo/circles/model/GroupListItem.kt
+++ b/app/src/main/java/org/futo/circles/model/GroupListItem.kt
@@ -24,12 +24,12 @@ data class InvitedGroupListItem(
     override val id: String,
     override val info: RoomInfo,
     val isEncrypted: Boolean,
-    val inviterName: String,
+    val inviterName: String
 ) : GroupListItem(id, info, Membership.INVITE)
 
 data class RequestGroupListItem(
     override val id: String,
     override val info: RoomInfo,
     val requesterName: String,
-    val requesterId: String,
+    val requesterId: String
 ) : GroupListItem(id, info, Membership.KNOCK)
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 704576132..666803df9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -84,10 +84,7 @@
     <string name="remove_from_this_circle_but_do_not_unfollow">Remove from this circle, but do not unfollow</string>
     <string name="unfollow_completely_remove_from_all_circles">Unfollow completely (remove from all circles)</string>
     <string name="unfollow">Unfollow</string>
-    <string name="decline">Decline</string>
-    <string name="invited_by_format">Invited by %s</string>
     <string name="requested_to_follow_format">%s requested to follow</string>
-    <string name="requested_to_join_format">%s requested to join</string>
     <string name="requested_to_join">Requested to join</string>
     <string name="select_circles_in_which_you_want_to_follow_this_timeline">Select circles in which you want to follow this timeline</string>
     <string name="accept_invite">Accept invite</string>
diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml
index 756bc5a1f..1ca60b490 100644
--- a/auth/src/main/res/values/strings.xml
+++ b/auth/src/main/res/values/strings.xml
@@ -52,7 +52,6 @@
     <string name="discard_current_registration_progress">Discard current registration progress?</string>
     <string name="domain">Domain</string>
     <string name="accept_terms_to_continue">Accept terms to continue</string>
-    <string name="accept">Accept</string>
     <string name="username">Username</string>
     <string name="at_symbol">\@</string>
     <string name="user_id_separator">:</string>
diff --git a/core/src/main/java/org/futo/circles/core/mapping/RoomSummaryMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/RoomSummaryMapping.kt
index 3f54b77ef..206481728 100644
--- a/core/src/main/java/org/futo/circles/core/mapping/RoomSummaryMapping.kt
+++ b/core/src/main/java/org/futo/circles/core/mapping/RoomSummaryMapping.kt
@@ -1,8 +1,12 @@
 package org.futo.circles.core.mapping
 
-import org.futo.circles.core.model.GalleryListItem
+import org.futo.circles.core.extensions.notEmptyDisplayName
+import org.futo.circles.core.model.InvitedGalleryListItem
+import org.futo.circles.core.model.JoinedGalleryListItem
 import org.futo.circles.core.model.RoomInfo
 import org.futo.circles.core.model.SelectableRoomListItem
+import org.futo.circles.core.provider.MatrixSessionProvider
+import org.matrix.android.sdk.api.session.getUserOrDefault
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 fun RoomSummary.nameOrId() = displayName.takeIf { it.isNotEmpty() } ?: roomId
@@ -18,7 +22,17 @@ fun RoomSummary.toSelectableRoomListItem(selected: Boolean = false) = Selectable
     isSelected = selected
 )
 
-fun RoomSummary.toGalleryListItem() = GalleryListItem(
+fun RoomSummary.toJoinedGalleryListItem() = JoinedGalleryListItem(
     id = roomId,
     info = toRoomInfo()
-)
\ No newline at end of file
+)
+
+fun RoomSummary.toInvitedGalleryListItem() = InvitedGalleryListItem(
+    id = roomId,
+    info = toRoomInfo(),
+    inviterName = getInviterName()
+)
+
+fun RoomSummary.getInviterName() =
+    MatrixSessionProvider.currentSession?.getUserOrDefault(inviterId ?: "")?.notEmptyDisplayName()
+        ?: ""
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/model/GalleryListItem.kt b/core/src/main/java/org/futo/circles/core/model/GalleryListItem.kt
index 5f32ac5be..9f904c5fd 100644
--- a/core/src/main/java/org/futo/circles/core/model/GalleryListItem.kt
+++ b/core/src/main/java/org/futo/circles/core/model/GalleryListItem.kt
@@ -1,9 +1,28 @@
 package org.futo.circles.core.model
 
 import org.futo.circles.core.list.IdEntity
-import org.futo.circles.core.model.RoomInfo
+import org.matrix.android.sdk.api.session.room.model.Membership
 
-data class GalleryListItem(
+sealed class GalleryListItem(
     override val id: String,
-    val info: RoomInfo
-) : IdEntity<String>
\ No newline at end of file
+    open val info: RoomInfo,
+    open val membership: Membership
+) : IdEntity<String>
+
+data class JoinedGalleryListItem(
+    override val id: String,
+    override val info: RoomInfo
+) : GalleryListItem(id, info, Membership.JOIN)
+
+data class InvitedGalleryListItem(
+    override val id: String,
+    override val info: RoomInfo,
+    val inviterName: String,
+) : GalleryListItem(id, info, Membership.INVITE)
+
+data class RequestGalleryListItem(
+    override val id: String,
+    override val info: RoomInfo,
+    val requesterName: String,
+    val requesterId: String
+) : GalleryListItem(id, info, Membership.KNOCK)
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryFragment.kt b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryFragment.kt
index 62c561b00..d41908252 100644
--- a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryFragment.kt
+++ b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryFragment.kt
@@ -10,7 +10,7 @@ import org.futo.circles.core.R
 import org.futo.circles.core.databinding.FragmentPickGalleryBinding
 import org.futo.circles.core.extensions.observeData
 import org.futo.circles.core.picker.gallery.PickGalleryMediaViewModel
-import org.futo.circles.core.picker.gallery.rooms.list.PhotosListAdapter
+import org.futo.circles.core.picker.gallery.rooms.list.PickGalleryListAdapter
 
 @AndroidEntryPoint
 class PickGalleryFragment : Fragment(R.layout.fragment_pick_gallery) {
@@ -20,7 +20,9 @@ class PickGalleryFragment : Fragment(R.layout.fragment_pick_gallery) {
     private val binding by viewBinding(FragmentPickGalleryBinding::bind)
 
     private val listAdapter by lazy {
-        PhotosListAdapter(onRoomClicked = { gallery -> parentViewModel.onGalleryChosen(gallery.id) })
+        PickGalleryListAdapter(onRoomClicked = { gallery ->
+            parentViewModel.onGalleryChosen(gallery.id)
+        })
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
diff --git a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryViewModel.kt b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryViewModel.kt
index 00293c4cd..59fb53647 100644
--- a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryViewModel.kt
+++ b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/PickGalleryViewModel.kt
@@ -3,9 +3,10 @@ package org.futo.circles.core.picker.gallery.rooms
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.map
 import dagger.hilt.android.lifecycle.HiltViewModel
-import org.futo.circles.core.mapping.toGalleryListItem
+import org.futo.circles.core.mapping.toJoinedGalleryListItem
 import org.futo.circles.core.model.GALLERY_TYPE
 import org.futo.circles.core.model.GalleryListItem
+import org.futo.circles.core.model.JoinedGalleryListItem
 import org.futo.circles.core.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -19,10 +20,10 @@ class PickGalleryViewModel @Inject constructor() : ViewModel() {
         ?.getRoomSummariesLive(roomSummaryQueryParams())
         ?.map { list -> filterGalleries(list) }
 
-    private fun filterGalleries(list: List<RoomSummary>): List<GalleryListItem> {
+    private fun filterGalleries(list: List<RoomSummary>): List<JoinedGalleryListItem> {
         return list.mapNotNull { summary ->
             if (summary.roomType == GALLERY_TYPE && summary.membership == Membership.JOIN) {
-                summary.toGalleryListItem()
+                summary.toJoinedGalleryListItem()
             } else null
         }
     }
diff --git a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/GalleryViewHolder.kt b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/GalleryViewHolder.kt
index 5691802b6..5c8977a1c 100644
--- a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/GalleryViewHolder.kt
+++ b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/GalleryViewHolder.kt
@@ -1,30 +1,97 @@
 package org.futo.circles.core.picker.gallery.rooms.list
 
+import android.view.View
 import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
-import org.futo.circles.core.databinding.ListItemGalleryBinding
+import org.futo.circles.core.R
+import org.futo.circles.core.databinding.ListItemInvitedGalleryBinding
+import org.futo.circles.core.databinding.ListItemJoinedGalleryBinding
+import org.futo.circles.core.databinding.ListItemRequestGalleryBinding
 import org.futo.circles.core.extensions.loadProfileIcon
 import org.futo.circles.core.extensions.onClick
 import org.futo.circles.core.list.ViewBindingHolder
+import org.futo.circles.core.list.context
 import org.futo.circles.core.model.GalleryListItem
+import org.futo.circles.core.model.InvitedGalleryListItem
+import org.futo.circles.core.model.JoinedGalleryListItem
+import org.futo.circles.core.model.RequestGalleryListItem
 
-class GalleryViewHolder(
+
+abstract class GalleryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+    abstract fun bind(data: GalleryListItem)
+}
+
+class JoinedGalleryViewHolder(
     parent: ViewGroup,
     onGalleryClicked: (Int) -> Unit
-) : RecyclerView.ViewHolder(inflate(parent, ListItemGalleryBinding::inflate)) {
+) : GalleryViewHolder(inflate(parent, ListItemJoinedGalleryBinding::inflate)) {
 
     private companion object : ViewBindingHolder
 
-    private val binding = baseBinding as ListItemGalleryBinding
+    private val binding = baseBinding as ListItemJoinedGalleryBinding
 
     init {
         onClick(itemView) { position -> onGalleryClicked(position) }
     }
 
-    fun bind(data: GalleryListItem) {
+    override fun bind(data: GalleryListItem) {
+        if (data !is JoinedGalleryListItem) return
+
         with(binding) {
             ivGalleryImage.loadProfileIcon(data.info.avatarUrl, "")
             tvGalleryName.text = data.info.title
         }
     }
+}
+
+class InvitedGalleryViewHolder(
+    parent: ViewGroup,
+    onInviteClicked: (Int, Boolean) -> Unit
+) : GalleryViewHolder(inflate(parent, ListItemInvitedGalleryBinding::inflate)) {
+
+    private companion object : ViewBindingHolder
+
+    private val binding = baseBinding as ListItemInvitedGalleryBinding
+
+    init {
+        onClick(binding.btnAccept) { position -> onInviteClicked(position, true) }
+        onClick(binding.btnDecline) { position -> onInviteClicked(position, false) }
+    }
+
+    override fun bind(data: GalleryListItem) {
+        if (data !is InvitedGalleryListItem) return
+
+        with(binding) {
+            tvGalleryTitle.text = data.info.title
+            ivGallery.loadProfileIcon(data.info.avatarUrl, data.info.title)
+            tvInviterName.text = context.getString(R.string.invited_by_format, data.inviterName)
+        }
+    }
+}
+
+class RequestGalleryViewHolder(
+    parent: ViewGroup,
+    onRequestClicked: (Int, Boolean) -> Unit
+) : GalleryViewHolder(inflate(parent, ListItemRequestGalleryBinding::inflate)) {
+
+    private companion object : ViewBindingHolder
+
+    private val binding = baseBinding as ListItemRequestGalleryBinding
+
+    init {
+        onClick(binding.btnInvite) { position -> onRequestClicked(position, true) }
+        onClick(binding.btnDecline) { position -> onRequestClicked(position, false) }
+    }
+
+    override fun bind(data: GalleryListItem) {
+        if (data !is RequestGalleryListItem) return
+
+        with(binding) {
+            binding.tvRequestUserId.text = context.getString(
+                R.string.requested_to_join_format, data.requesterName
+            )
+            binding.tvRoomName.text = data.info.title
+            ivGallery.loadProfileIcon(data.info.avatarUrl, data.info.title)
+        }
+    }
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/PhotosListAdapter.kt b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/PickGalleryListAdapter.kt
similarity index 59%
rename from core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/PhotosListAdapter.kt
rename to core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/PickGalleryListAdapter.kt
index 2909e4ad7..331bbf201 100644
--- a/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/PhotosListAdapter.kt
+++ b/core/src/main/java/org/futo/circles/core/picker/gallery/rooms/list/PickGalleryListAdapter.kt
@@ -1,22 +1,25 @@
 package org.futo.circles.core.picker.gallery.rooms.list
 
+
 import android.view.ViewGroup
 import org.futo.circles.core.list.BaseRvAdapter
 import org.futo.circles.core.model.GalleryListItem
 
-class PhotosListAdapter(
-    private val onRoomClicked: (GalleryListItem) -> Unit
-) : BaseRvAdapter<GalleryListItem, GalleryViewHolder>(DefaultIdEntityCallback()) {
+
+class PickGalleryListAdapter(
+    private val onRoomClicked: (GalleryListItem) -> Unit,
+) : BaseRvAdapter<GalleryListItem, JoinedGalleryViewHolder>(DefaultIdEntityCallback()) {
+
 
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
-    ) = GalleryViewHolder(
+    ) = JoinedGalleryViewHolder(
         parent = parent,
         onGalleryClicked = { position -> onRoomClicked(getItem(position)) }
     )
 
-    override fun onBindViewHolder(holder: GalleryViewHolder, position: Int) {
+    override fun onBindViewHolder(holder: JoinedGalleryViewHolder, position: Int) {
         holder.bind(getItem(position))
     }
 
diff --git a/core/src/main/res/layout/list_item_invited_gallery.xml b/core/src/main/res/layout/list_item_invited_gallery.xml
new file mode 100644
index 000000000..94d9797d4
--- /dev/null
+++ b/core/src/main/res/layout/list_item_invited_gallery.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?selectableItemBackground"
+    android:clickable="true"
+    android:focusable="true"
+    android:padding="4dp">
+
+
+    <com.google.android.material.imageview.ShapeableImageView
+        android:id="@+id/ivGallery"
+        android:layout_width="@dimen/group_icon_size"
+        android:layout_height="@dimen/group_icon_size"
+        android:scaleType="centerCrop"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.GroupIconRadius"
+        tools:src="@color/blue" />
+
+
+    <TextView
+        android:id="@+id/tvGalleryTitle"
+        style="@style/title2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="4dp"
+        android:ellipsize="end"
+        android:lines="1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivGallery"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="texsdt" />
+
+
+    <TextView
+        android:id="@+id/tvInviterName"
+        style="@style/subheadline"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:ellipsize="end"
+        android:lines="1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@id/tvGalleryTitle"
+        app:layout_constraintTop_toBottomOf="@id/tvGalleryTitle"
+        tools:text="texsdt" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnAccept"
+        style="@style/AccentButtonStyle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:text="@string/accept"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/btnDecline"
+        app:layout_constraintStart_toStartOf="@id/tvGalleryTitle"
+        app:layout_constraintTop_toBottomOf="@+id/tvInviterName" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnDecline"
+        style="@style/NegativeButtonStyle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:text="@string/decline"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/btnAccept"
+        app:layout_constraintTop_toBottomOf="@+id/tvInviterName" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/core/src/main/res/layout/list_item_gallery.xml b/core/src/main/res/layout/list_item_joined_gallery.xml
similarity index 100%
rename from core/src/main/res/layout/list_item_gallery.xml
rename to core/src/main/res/layout/list_item_joined_gallery.xml
diff --git a/core/src/main/res/layout/list_item_request_gallery.xml b/core/src/main/res/layout/list_item_request_gallery.xml
new file mode 100644
index 000000000..6a83fb431
--- /dev/null
+++ b/core/src/main/res/layout/list_item_request_gallery.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?selectableItemBackground"
+    android:clickable="true"
+    android:focusable="true"
+    android:padding="4dp">
+
+    <com.google.android.material.imageview.ShapeableImageView
+        android:id="@+id/ivGallery"
+        android:layout_width="@dimen/group_icon_size"
+        android:layout_height="@dimen/group_icon_size"
+        android:scaleType="centerCrop"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.GroupIconRadius"
+        tools:src="@color/blue" />
+
+
+    <TextView
+        android:id="@+id/tvRequestUserId"
+        style="@style/body"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:ellipsize="end"
+        android:lines="1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivGallery"
+        app:layout_constraintTop_toTopOf="@id/ivGallery"
+        app:layout_constraintVertical_chainStyle="packed"
+        tools:text="texsdt" />
+
+    <TextView
+        android:id="@+id/tvRoomName"
+        style="@style/body"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:ellipsize="end"
+        android:lines="1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivGallery"
+        app:layout_constraintTop_toBottomOf="@+id/tvRequestUserId"
+        app:layout_constraintVertical_chainStyle="packed"
+        tools:text="texsdt" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnInvite"
+        style="@style/AccentButtonStyle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:text="@string/invite"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/btnDecline"
+        app:layout_constraintStart_toStartOf="@id/tvRequestUserId"
+        app:layout_constraintTop_toBottomOf="@id/tvRoomName" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnDecline"
+        style="@style/NegativeButtonStyle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:text="@string/decline"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toBottomOf="@id/btnInvite"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/btnInvite"
+        app:layout_constraintTop_toTopOf="@id/btnInvite" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index e91300ed4..5c4f97558 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -125,5 +125,9 @@
     <string name="gallery_updated">Gallery updated</string>
     <string name="last_updated_formatter">Last updated %s</string>
     <string name="delete_gallery_message">Are you sure you want to remove this gallery?</string>
+    <string name="accept">Accept</string>
+    <string name="decline">Decline</string>
+    <string name="requested_to_join_format">%s requested to join</string>
+    <string name="invited_by_format">Invited by %s</string>
 
 </resources>
\ No newline at end of file
diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosDataSource.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosDataSource.kt
index bd0116872..092931b58 100644
--- a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosDataSource.kt
+++ b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosDataSource.kt
@@ -1,10 +1,20 @@
 package org.futo.circles.gallery.feature
 
-import androidx.lifecycle.map
-import org.futo.circles.core.mapping.toGalleryListItem
+import androidx.lifecycle.asFlow
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.withContext
+import org.futo.circles.core.mapping.toInvitedGalleryListItem
+import org.futo.circles.core.mapping.toJoinedGalleryListItem
+import org.futo.circles.core.mapping.toRoomInfo
 import org.futo.circles.core.model.GALLERY_TYPE
 import org.futo.circles.core.model.GalleryListItem
+import org.futo.circles.core.model.RequestGalleryListItem
 import org.futo.circles.core.provider.MatrixSessionProvider
+import org.futo.circles.core.utils.UserUtils
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
@@ -12,15 +22,52 @@ import javax.inject.Inject
 
 class PhotosDataSource @Inject constructor() {
 
-    fun getGalleriesLiveData() = MatrixSessionProvider.currentSession?.roomService()
-        ?.getRoomSummariesLive(roomSummaryQueryParams())
-        ?.map { list -> filterGalleries(list) }
+    fun getGalleriesFlow() = combine(
+        MatrixSessionProvider.getSessionOrThrow().roomService()
+            .getRoomSummariesLive(roomSummaryQueryParams()).asFlow(),
+        MatrixSessionProvider.getSessionOrThrow().roomService().getChangeMembershipsLive().asFlow()
+    ) { roomSummaries, _ ->
+        withContext(Dispatchers.IO) { filterGalleries(roomSummaries) }
+    }.distinctUntilChanged()
+
 
     private fun filterGalleries(list: List<RoomSummary>): List<GalleryListItem> {
-        return list.mapNotNull { summary ->
-            if (summary.roomType == GALLERY_TYPE && summary.membership == Membership.JOIN) {
-                summary.toGalleryListItem()
-            } else null
+        val groups = list.filter { it.roomType == GALLERY_TYPE }
+        val joined = groups.mapNotNull { it.takeIf { it.membership == Membership.JOIN } }
+        val invites = groups.mapNotNull { it.takeIf { it.membership == Membership.INVITE } }
+        val knocks = getKnockRequestToJoinedGroups(joined)
+        return mutableListOf<GalleryListItem>().apply {
+            addAll(knocks)
+            addAll(invites.map { it.toInvitedGalleryListItem() })
+            addAll(joined.map { it.toJoinedGalleryListItem() })
+        }
+    }
+
+    private fun getKnockRequestToJoinedGroups(joined: List<RoomSummary>): List<RequestGalleryListItem> {
+        val requests = mutableListOf<RequestGalleryListItem>()
+
+        joined.forEach { groupSummary ->
+            val group =
+                MatrixSessionProvider.currentSession?.getRoom(groupSummary.roomId) ?: return@forEach
+
+            val knockingMembers =
+                group.membershipService().getRoomMembers(roomMemberQueryParams {
+                    memberships = listOf(Membership.KNOCK)
+                }).takeIf { it.isNotEmpty() } ?: return@forEach
+
+
+            knockingMembers.forEach { user ->
+                requests.add(
+                    RequestGalleryListItem(
+                        id = groupSummary.roomId,
+                        info = groupSummary.toRoomInfo(),
+                        requesterName = user.displayName
+                            ?: UserUtils.removeDomainSuffix(user.userId),
+                        requesterId = user.userId
+                    )
+                )
+            }
         }
+        return requests
     }
-}
\ No newline at end of file
+}
diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosFragment.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosFragment.kt
index 172e1311d..0f1004782 100644
--- a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosFragment.kt
+++ b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosFragment.kt
@@ -20,11 +20,12 @@ import org.futo.circles.core.CirclesAppConfig
 import org.futo.circles.core.databinding.FragmentRoomsBinding
 import org.futo.circles.core.extensions.navigateSafe
 import org.futo.circles.core.extensions.observeData
-import org.futo.circles.core.picker.helper.RuntimePermissionHelper
-import org.futo.circles.gallery.R
-import org.futo.circles.core.picker.gallery.rooms.list.PhotosListAdapter
+import org.futo.circles.core.extensions.observeResponse
 import org.futo.circles.core.model.GalleryListItem
+import org.futo.circles.core.model.RequestGalleryListItem
+import org.futo.circles.core.picker.helper.RuntimePermissionHelper
 import org.futo.circles.core.view.EmptyTabPlaceholderView
+import org.futo.circles.gallery.R
 
 @AndroidEntryPoint
 class PhotosFragment : Fragment(org.futo.circles.core.R.layout.fragment_rooms), MenuProvider {
@@ -33,7 +34,14 @@ class PhotosFragment : Fragment(org.futo.circles.core.R.layout.fragment_rooms),
     private val binding by viewBinding(FragmentRoomsBinding::bind)
 
     private val listAdapter by lazy {
-        PhotosListAdapter(onRoomClicked = { roomListItem -> onRoomListItemClicked(roomListItem) })
+        PhotosListAdapter(
+            onRoomClicked = { roomListItem -> onRoomListItemClicked(roomListItem) },
+            onInviteClicked = { roomListItem, isAccepted ->
+                onInviteClicked(roomListItem, isAccepted)
+            },
+            onRequestClicked = { roomListItem, isAccepted ->
+                onRequestClicked(roomListItem, isAccepted)
+            })
     }
 
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
@@ -75,7 +83,8 @@ class PhotosFragment : Fragment(org.futo.circles.core.R.layout.fragment_rooms),
     }
 
     private fun setupObservers() {
-        viewModel.roomsLiveData?.observeData(this) { listAdapter.submitList(it) }
+        viewModel.roomsLiveData.observeData(this) { listAdapter.submitList(it) }
+        viewModel.inviteResultLiveData.observeResponse(this)
     }
 
     private fun onRoomListItemClicked(room: GalleryListItem) {
@@ -88,6 +97,16 @@ class PhotosFragment : Fragment(org.futo.circles.core.R.layout.fragment_rooms),
         else navigateToBackupSettings()
     }
 
+    private fun onInviteClicked(room: GalleryListItem, isAccepted: Boolean) {
+        if (isAccepted) viewModel.acceptPhotosInvite(room.id)
+        else viewModel.rejectInvite(room.id)
+    }
+
+    private fun onRequestClicked(room: RequestGalleryListItem, isAccepted: Boolean) {
+        if (isAccepted) viewModel.inviteUser(room)
+        else viewModel.kickUser(room)
+    }
+
     private fun navigateToCreateRoom() {
         findNavController().navigateSafe(PhotosFragmentDirections.toCreateGalleryDialogFragment())
     }
diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosListAdapter.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosListAdapter.kt
new file mode 100644
index 000000000..22423eabd
--- /dev/null
+++ b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosListAdapter.kt
@@ -0,0 +1,58 @@
+package org.futo.circles.gallery.feature
+
+import android.view.ViewGroup
+import org.futo.circles.core.list.BaseRvAdapter
+import org.futo.circles.core.model.GalleryListItem
+import org.futo.circles.core.model.InvitedGalleryListItem
+import org.futo.circles.core.model.JoinedGalleryListItem
+import org.futo.circles.core.model.RequestGalleryListItem
+import org.futo.circles.core.picker.gallery.rooms.list.GalleryViewHolder
+import org.futo.circles.core.picker.gallery.rooms.list.InvitedGalleryViewHolder
+import org.futo.circles.core.picker.gallery.rooms.list.JoinedGalleryViewHolder
+import org.futo.circles.core.picker.gallery.rooms.list.RequestGalleryViewHolder
+
+private enum class GalleryListItemViewType { JoinedGallery, InvitedGallery, KnockRequest }
+
+class PhotosListAdapter(
+    private val onRoomClicked: (GalleryListItem) -> Unit,
+    private val onInviteClicked: (GalleryListItem, Boolean) -> Unit,
+    private val onRequestClicked: (RequestGalleryListItem, Boolean) -> Unit
+) : BaseRvAdapter<GalleryListItem, GalleryViewHolder>(DefaultIdEntityCallback()) {
+
+    override fun getItemViewType(position: Int): Int = when (getItem(position)) {
+        is JoinedGalleryListItem -> GalleryListItemViewType.JoinedGallery.ordinal
+        is InvitedGalleryListItem -> GalleryListItemViewType.InvitedGallery.ordinal
+        is RequestGalleryListItem -> GalleryListItemViewType.KnockRequest.ordinal
+    }
+
+    override fun onCreateViewHolder(
+        parent: ViewGroup,
+        viewType: Int
+    ) = when (GalleryListItemViewType.values()[viewType]) {
+        GalleryListItemViewType.JoinedGallery -> JoinedGalleryViewHolder(
+            parent = parent,
+            onGalleryClicked = { position -> onRoomClicked(getItem(position)) }
+        )
+
+        GalleryListItemViewType.InvitedGallery -> InvitedGalleryViewHolder(
+            parent = parent,
+            onInviteClicked = { position, isAccepted ->
+                onInviteClicked(getItem(position), isAccepted)
+            }
+        )
+
+        GalleryListItemViewType.KnockRequest -> RequestGalleryViewHolder(
+            parent = parent,
+            onRequestClicked = { position, isAccepted ->
+                (getItem(position) as? RequestGalleryListItem)?.let {
+                    onRequestClicked(it, isAccepted)
+                }
+            }
+        )
+    }
+
+    override fun onBindViewHolder(holder: GalleryViewHolder, position: Int) {
+        holder.bind(getItem(position))
+    }
+
+}
\ No newline at end of file
diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosViewModel.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosViewModel.kt
index f9aa6e6a0..f04479d3d 100644
--- a/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosViewModel.kt
+++ b/gallery/src/main/java/org/futo/circles/gallery/feature/PhotosViewModel.kt
@@ -1,14 +1,48 @@
 package org.futo.circles.gallery.feature
 
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
 import dagger.hilt.android.lifecycle.HiltViewModel
+import org.futo.circles.core.SingleEventLiveData
+import org.futo.circles.core.extensions.Response
+import org.futo.circles.core.extensions.launchBg
+import org.futo.circles.core.model.CircleRoomTypeArg
+import org.futo.circles.core.model.RequestGalleryListItem
+import org.futo.circles.core.room.invite.InviteRequestsDataSource
 import javax.inject.Inject
 
 @HiltViewModel
 class PhotosViewModel @Inject constructor(
-    dataSource: PhotosDataSource
+    dataSource: PhotosDataSource,
+    private val inviteRequestsDataSource: InviteRequestsDataSource
 ) : ViewModel() {
 
-    val roomsLiveData = dataSource.getGalleriesLiveData()
+    val roomsLiveData = dataSource.getGalleriesFlow().asLiveData()
+    val inviteResultLiveData = SingleEventLiveData<Response<Unit?>>()
+
+    fun rejectInvite(roomId: String) {
+        launchBg {
+            val result = inviteRequestsDataSource.rejectInvite(roomId)
+            inviteResultLiveData.postValue(result)
+        }
+    }
+
+    fun acceptPhotosInvite(roomId: String) {
+        launchBg {
+            val result = inviteRequestsDataSource.acceptInvite(roomId, CircleRoomTypeArg.Photo)
+            inviteResultLiveData.postValue(result)
+        }
+    }
+
+    fun inviteUser(room: RequestGalleryListItem) {
+        launchBg {
+            val result = inviteRequestsDataSource.inviteUser(room.id, room.requesterId)
+            inviteResultLiveData.postValue(result)
+        }
+    }
+
+    fun kickUser(room: RequestGalleryListItem) {
+        launchBg { inviteRequestsDataSource.kickUser(room.id, room.requesterId) }
+    }
 
 }
\ No newline at end of file
diff --git a/gallery/src/main/res/layout/list_item_select_gallery.xml b/gallery/src/main/res/layout/list_item_select_gallery.xml
index f4bf56930..d77f23155 100644
--- a/gallery/src/main/res/layout/list_item_select_gallery.xml
+++ b/gallery/src/main/res/layout/list_item_select_gallery.xml
@@ -7,7 +7,7 @@
 
     <include
         android:id="@+id/baseGalleryItem"
-        layout="@layout/list_item_gallery"
+        layout="@layout/list_item_joined_gallery"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         app:layout_constraintEnd_toEndOf="parent"
-- 
GitLab