From 2b59ad04c7070bfa7730b0ad5d3638385198954c Mon Sep 17 00:00:00 2001
From: Taras Smakula <tarassmakula@gmail.com>
Date: Tue, 2 Jan 2024 16:08:24 +0200
Subject: [PATCH] Change people tab categories

---
 .../feature/people/PeopleDataSource.kt        | 131 +++++++++++++-----
 .../futo/circles/mapping/MatrixUserMapping.kt |   4 +-
 .../org/futo/circles/model/PeopleListItem.kt  |  14 +-
 app/src/main/res/values/strings.xml           |   1 +
 .../extensions/MatrixSessionExtensions.kt     |  25 ++--
 5 files changed, 119 insertions(+), 56 deletions(-)

diff --git a/app/src/main/java/org/futo/circles/feature/people/PeopleDataSource.kt b/app/src/main/java/org/futo/circles/feature/people/PeopleDataSource.kt
index 8aec2dbca..a18cad47b 100644
--- a/app/src/main/java/org/futo/circles/feature/people/PeopleDataSource.kt
+++ b/app/src/main/java/org/futo/circles/feature/people/PeopleDataSource.kt
@@ -8,23 +8,32 @@ import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.withContext
+import org.futo.circles.core.extensions.getRoomOwner
 import org.futo.circles.core.feature.room.knoks.KnockRequestsDataSource
 import org.futo.circles.core.feature.select_users.SearchUserDataSource
 import org.futo.circles.core.feature.workspace.SharedCircleDataSource
+import org.futo.circles.core.feature.workspace.SpacesTreeAccountDataSource
+import org.futo.circles.core.model.CIRCLES_SPACE_ACCOUNT_DATA_KEY
 import org.futo.circles.core.provider.MatrixSessionProvider
+import org.futo.circles.core.utils.getJoinedRoomById
+import org.futo.circles.core.utils.getTimelineRoomFor
 import org.futo.circles.mapping.toPeopleUserListItem
 import org.futo.circles.model.PeopleHeaderItem
 import org.futo.circles.model.PeopleItemType
 import org.futo.circles.model.PeopleListItem
 import org.futo.circles.model.PeopleRequestNotificationListItem
-import org.matrix.android.sdk.api.session.getRoom
+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.RoomType
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.user.model.User
 import javax.inject.Inject
 
 class PeopleDataSource @Inject constructor(
     private val searchUserDataSource: SearchUserDataSource,
     private val sharedCircleDataSource: SharedCircleDataSource,
-    private val knockRequestsDataSource: KnockRequestsDataSource
+    private val knockRequestsDataSource: KnockRequestsDataSource,
+    private val spacesTreeAccountDataSource: SpacesTreeAccountDataSource
 ) {
 
     private val session = MatrixSessionProvider.currentSession
@@ -35,18 +44,28 @@ class PeopleDataSource @Inject constructor(
             it.size
         }?.asFlow() ?: flowOf()
 
+    private fun getProfileSpaceInvitesCountFlow() = session?.roomService()?.getRoomSummariesLive(
+        roomSummaryQueryParams {
+            excludeType = null
+            memberships = listOf(Membership.INVITE)
+        })?.map { it.filter { it.roomType == RoomType.SPACE } }?.map { it.size }
+        ?.asFlow() ?: flowOf()
+
+
     suspend fun getPeopleList(query: String) = combine(
         searchUserDataSource.searchKnownUsers(query),
         searchUserDataSource.searchSuggestions(query),
         getIgnoredUserFlow(),
-        getKnockRequestCountFlow()
-    ) { knowUsers, suggestions, ignoredUsers, requestsCount ->
+        getKnockRequestCountFlow(),
+        getProfileSpaceInvitesCountFlow()
+    ) { knowUsers, suggestions, ignoredUsers, knocksCount, profileInvitesCount ->
         withContext(Dispatchers.IO) {
             buildList(
                 knowUsers,
                 suggestions,
                 ignoredUsers,
-                requestsCount
+                knocksCount,
+                profileInvitesCount
             )
         }
     }.distinctUntilChanged()
@@ -63,54 +82,100 @@ class PeopleDataSource @Inject constructor(
         knowUsers: List<User>,
         suggestions: List<User>,
         ignoredUsers: List<User>,
-        requestsCount: Int
+        knocksCount: Int,
+        profileInvitesCount: Int
     ): List<PeopleListItem> {
+        val knownIds = knowUsers.map { it.userId }
         val ignoredUsersIds = ignoredUsers.map { it.userId }.toSet()
-        val uniqueItemsList = mutableListOf<PeopleListItem>().apply {
-            addAll(knowUsers.map { it.toPeopleUserListItem(getKnownUserItemType(it.userId)) })
-            addAll(suggestions.map { it.toPeopleUserListItem(PeopleItemType.Suggestion) })
-        }
-            .distinctBy { it.id }
-            .filterNot { it.id == session?.myUserId || ignoredUsersIds.contains(it.id) }
+        val followingUsersIds = getPeopleImFollowingIds()
+        val followersUsersIds = getFollowersIds()
+        val connectionsIds =
+            knowUsers.mapNotNull { if (isConnection(it.userId)) it.userId else null }
+        val otherMemberIds =
+            knownIds - connectionsIds.toSet() - followersUsersIds.toSet() - followingUsersIds.toSet()
+        val uniqueSuggestions = suggestions.filter { !knownIds.contains(it.userId) }
+
+        val requestsCount = knocksCount + profileInvitesCount
 
         return mutableListOf<PeopleListItem>().apply {
             if (requestsCount > 0)
                 add(PeopleRequestNotificationListItem(requestsCount))
 
             addSection(
-                PeopleHeaderItem.friends,
-                uniqueItemsList.filter { it.type == PeopleItemType.Friend }
+                PeopleHeaderItem.connections,
+                knowUsers.mapNotNull {
+                    if (connectionsIds.contains(it.userId)) it.toPeopleUserListItem(
+                        PeopleItemType.Connection,
+                        ignoredUsersIds.contains(it.userId)
+                    ) else null
+                }
             )
             addSection(
                 PeopleHeaderItem.followingUsersHeader,
-                uniqueItemsList.filter { it.type == PeopleItemType.Following }
+                knowUsers.mapNotNull {
+                    if (followingUsersIds.contains(it.userId)) it.toPeopleUserListItem(
+                        PeopleItemType.Following,
+                        ignoredUsersIds.contains(it.userId)
+                    ) else null
+                }
             )
             addSection(
                 PeopleHeaderItem.followersUsersHeader,
-                uniqueItemsList.filter { it.type == PeopleItemType.Follower }
+                knowUsers.mapNotNull {
+                    if (followersUsersIds.contains(it.userId)) it.toPeopleUserListItem(
+                        PeopleItemType.Follower,
+                        ignoredUsersIds.contains(it.userId)
+                    ) else null
+                }
             )
             addSection(
-                PeopleHeaderItem.knownUsersHeader,
-                uniqueItemsList.filter { it.type == PeopleItemType.Known }
+                PeopleHeaderItem.othersHeader,
+                knowUsers.mapNotNull {
+                    if (otherMemberIds.contains(it.userId)) it.toPeopleUserListItem(
+                        PeopleItemType.Others,
+                        ignoredUsersIds.contains(it.userId)
+                    ) else null
+                }
             )
             addSection(
                 PeopleHeaderItem.suggestions,
-                uniqueItemsList.filter { it.type == PeopleItemType.Suggestion }
+                uniqueSuggestions.map {
+                    it.toPeopleUserListItem(
+                        PeopleItemType.Suggestion,
+                        ignoredUsersIds.contains(it.userId)
+                    )
+                }
             )
         }
     }
 
-    private fun getKnownUserItemType(userId: String): PeopleItemType {
-        val isFollower = isMyFollower(userId)
-        val amIFollowing = amIFollowing(userId)
-        val isFriend = isFollower && amIFollowing
+    //All the joined members (except me) in all of my circle timeline rooms
+    private fun getFollowersIds(): List<String> {
+        val myCirclesSpace = getMyCirclesSpaceSummary() ?: return emptyList()
+        val myTimelinesFollowers = myCirclesSpace.spaceChildren?.mapNotNull {
+            getTimelineRoomFor(it.childRoomId)?.roomSummary()?.otherMemberIds
+        }?.flatMap { it.toSet() } ?: emptyList()
 
-        return when {
-            isFriend -> PeopleItemType.Friend
-            amIFollowing -> PeopleItemType.Following
-            isFollower -> PeopleItemType.Follower
-            else -> PeopleItemType.Known
-        }
+        return myTimelinesFollowers
+    }
+
+    //All the creators of all the timeline rooms that I'm following in my circles
+    private fun getPeopleImFollowingIds(): List<String> {
+        val myCirclesSpace = getMyCirclesSpaceSummary() ?: return emptyList()
+        val peopleIamFollowing = myCirclesSpace.spaceChildren?.mapNotNull {
+            getJoinedRoomById(it.childRoomId)?.roomSummary()?.spaceChildren?.mapNotNull {
+                getRoomOwner(it.childRoomId)?.userId?.takeIf { it != session?.myUserId }
+            }
+        }?.flatMap { it.toSet() } ?: emptyList()
+
+        return peopleIamFollowing
+    }
+
+    private fun getMyCirclesSpaceSummary(): RoomSummary? {
+        val circlesSpaceId = spacesTreeAccountDataSource.getRoomIdByKey(
+            CIRCLES_SPACE_ACCOUNT_DATA_KEY
+        ) ?: ""
+        return getJoinedRoomById(circlesSpaceId)?.roomSummary()
     }
 
     private fun MutableList<PeopleListItem>.addSection(
@@ -123,13 +188,7 @@ class PeopleDataSource @Inject constructor(
         }
     }
 
-    private fun isMyFollower(userId: String): Boolean {
-        val mySharedCircleMembers =
-            session?.getRoom(profileRoomId)?.roomSummary()?.otherMemberIds ?: emptyList()
-        return mySharedCircleMembers.contains(userId)
-    }
-
-    private fun amIFollowing(userId: String) =
+    private fun isConnection(userId: String) =
         sharedCircleDataSource.getSharedCircleFor(userId) != null
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/mapping/MatrixUserMapping.kt b/app/src/main/java/org/futo/circles/mapping/MatrixUserMapping.kt
index 2dd4b77ac..5466958d5 100644
--- a/app/src/main/java/org/futo/circles/mapping/MatrixUserMapping.kt
+++ b/app/src/main/java/org/futo/circles/mapping/MatrixUserMapping.kt
@@ -6,7 +6,7 @@ import org.futo.circles.model.PeopleUserListItem
 import org.matrix.android.sdk.api.session.user.model.User
 
 
-fun User.toPeopleUserListItem(type: PeopleItemType) =
-    PeopleUserListItem(toCirclesUserSummary(), type)
+fun User.toPeopleUserListItem(type: PeopleItemType, isIgnored: Boolean) =
+    PeopleUserListItem(toCirclesUserSummary(), type, isIgnored)
 
 
diff --git a/app/src/main/java/org/futo/circles/model/PeopleListItem.kt b/app/src/main/java/org/futo/circles/model/PeopleListItem.kt
index 1fa1cf1f7..66127074e 100644
--- a/app/src/main/java/org/futo/circles/model/PeopleListItem.kt
+++ b/app/src/main/java/org/futo/circles/model/PeopleListItem.kt
@@ -4,7 +4,7 @@ import org.futo.circles.R
 import org.futo.circles.core.base.list.IdEntity
 import org.futo.circles.core.model.CirclesUserSummary
 
-enum class PeopleItemType { Header, Friend, Following, Follower, RequestNotification, Known, Suggestion }
+enum class PeopleItemType { Header, Connection, Following, Follower, RequestNotification, Others, Suggestion }
 sealed class PeopleListItem(
     open val type: PeopleItemType
 ) : IdEntity<String>
@@ -15,17 +15,19 @@ data class PeopleHeaderItem(
     override val id: String = titleRes.toString()
 
     companion object {
-        val friends = PeopleHeaderItem(org.futo.circles.auth.R.string.friends)
-        val followersUsersHeader = PeopleHeaderItem(R.string.followers)
-        val followingUsersHeader = PeopleHeaderItem(org.futo.circles.core.R.string.following)
-        val knownUsersHeader = PeopleHeaderItem(R.string.known_users)
+        val connections = PeopleHeaderItem(R.string.my_connections)
+        val followersUsersHeader = PeopleHeaderItem(org.futo.circles.core.R.string.my_followers)
+        val followingUsersHeader =
+            PeopleHeaderItem(org.futo.circles.core.R.string.people_i_m_following)
+        val othersHeader = PeopleHeaderItem(org.futo.circles.core.R.string.others)
         val suggestions = PeopleHeaderItem(R.string.suggestions)
     }
 }
 
 class PeopleUserListItem(
     val user: CirclesUserSummary,
-    override val type: PeopleItemType
+    override val type: PeopleItemType,
+    val isIgnored: Boolean
 ) : PeopleListItem(type) {
     override val id: String = user.id
 }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a7734f17..bcc468ab7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -203,6 +203,7 @@
     <string name="group_invites_notification_format">Show %d Group invitations</string>
     <string name="circle_invites_notification_format">Show %d Circle invitations</string>
     <string name="show_follow_requests_format">Show %d follow requests</string>
+    <string name="my_connections">My Connections</string>
 
     <!-- Rich text editor -->
     <string name="rich_text_editor_format_bold">Apply bold format</string>
diff --git a/core/src/main/java/org/futo/circles/core/extensions/MatrixSessionExtensions.kt b/core/src/main/java/org/futo/circles/core/extensions/MatrixSessionExtensions.kt
index ea448a20b..67e555547 100644
--- a/core/src/main/java/org/futo/circles/core/extensions/MatrixSessionExtensions.kt
+++ b/core/src/main/java/org/futo/circles/core/extensions/MatrixSessionExtensions.kt
@@ -54,16 +54,17 @@ suspend fun Session.getOrFetchUser(userId: String): User =
     getUser(userId) ?: userService().resolveUser(userId)
 
 fun Session.getKnownUsersFlow() = roomService().getRoomSummariesLive(roomSummaryQueryParams {
-        memberships = listOf(Membership.JOIN)
-    }).asFlow()
-        .mapLatest { roomSummaries ->
-            val knowUsers = mutableSetOf<User>()
-            roomSummaries.forEach { summary ->
-                val joinedMembersIds = getRoom(summary.roomId)?.membershipService()
-                    ?.getRoomMembers(roomMemberQueryParams {
-                        memberships = listOf(Membership.JOIN)
-                    })?.map { it.userId } ?: emptyList()
-                joinedMembersIds.forEach { knowUsers.add(getOrFetchUser(it)) }
-            }
-            knowUsers.toList().filterNot { getUserIdsToExclude().contains(it.userId) }
+    excludeType = null
+    memberships = listOf(Membership.JOIN)
+}).asFlow()
+    .mapLatest { roomSummaries ->
+        val knowUsers = mutableSetOf<User>()
+        roomSummaries.forEach { summary ->
+            val joinedMembersIds = getRoom(summary.roomId)?.membershipService()
+                ?.getRoomMembers(roomMemberQueryParams {
+                    memberships = listOf(Membership.JOIN)
+                })?.map { it.userId } ?: emptyList()
+            joinedMembersIds.forEach { knowUsers.add(getOrFetchUser(it)) }
         }
+        knowUsers.toList().filterNot { getUserIdsToExclude().contains(it.userId) }
+    }
-- 
GitLab