mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Merge pull request #570 from vector-im/feature/left_group
Handle left group from sync
This commit is contained in:
commit
5651ea515b
12 changed files with 95 additions and 40 deletions
|
@ -15,6 +15,7 @@ Other changes:
|
||||||
Bugfix:
|
Bugfix:
|
||||||
- Fix characters erased from the Search field when the result are coming (#545)
|
- Fix characters erased from the Search field when the result are coming (#545)
|
||||||
- "No connection" banner was displayed by mistake
|
- "No connection" banner was displayed by mistake
|
||||||
|
- Leaving community (from another client) has no effect on RiotX (#497)
|
||||||
|
|
||||||
Translations:
|
Translations:
|
||||||
-
|
-
|
||||||
|
|
|
@ -16,12 +16,15 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.group.model
|
package im.vector.matrix.android.api.session.group.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds some data of a group.
|
* This class holds some data of a group.
|
||||||
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
|
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
|
||||||
*/
|
*/
|
||||||
data class GroupSummary(
|
data class GroupSummary(
|
||||||
val groupId: String,
|
val groupId: String,
|
||||||
|
val membership: Membership,
|
||||||
val displayName: String = "",
|
val displayName: String = "",
|
||||||
val shortDescription: String = "",
|
val shortDescription: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
|
|
|
@ -22,14 +22,15 @@ import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
|
|
||||||
internal object GroupSummaryMapper {
|
internal object GroupSummaryMapper {
|
||||||
|
|
||||||
fun map(roomSummaryEntity: GroupSummaryEntity): GroupSummary {
|
fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
|
||||||
return GroupSummary(
|
return GroupSummary(
|
||||||
roomSummaryEntity.groupId,
|
groupSummaryEntity.groupId,
|
||||||
roomSummaryEntity.displayName,
|
groupSummaryEntity.membership,
|
||||||
roomSummaryEntity.shortDescription,
|
groupSummaryEntity.displayName,
|
||||||
roomSummaryEntity.avatarUrl,
|
groupSummaryEntity.shortDescription,
|
||||||
roomSummaryEntity.roomIds.toList(),
|
groupSummaryEntity.avatarUrl,
|
||||||
roomSummaryEntity.userIds.toList()
|
groupSummaryEntity.roomIds.toList(),
|
||||||
|
groupSummaryEntity.userIds.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,13 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class GroupEntity(@PrimaryKey var groupId: String = ""
|
/**
|
||||||
|
* This class is used to store group info (groupId and membership) from the sync response.
|
||||||
) : RealmObject() {
|
* Then [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] observes change and
|
||||||
|
* makes requests to fetch group information from the homeserver
|
||||||
|
*/
|
||||||
|
internal open class GroupEntity(@PrimaryKey var groupId: String = "")
|
||||||
|
: RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
var membership: Membership
|
var membership: Membership
|
||||||
|
|
|
@ -16,18 +16,28 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.model
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
|
internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
|
||||||
var displayName: String = "",
|
var displayName: String = "",
|
||||||
var shortDescription: String = "",
|
var shortDescription: String = "",
|
||||||
var avatarUrl: String = "",
|
var avatarUrl: String = "",
|
||||||
var roomIds: RealmList<String> = RealmList(),
|
var roomIds: RealmList<String> = RealmList(),
|
||||||
var userIds: RealmList<String> = RealmList()
|
var userIds: RealmList<String> = RealmList()
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
var membership: Membership
|
||||||
|
get() {
|
||||||
|
return Membership.valueOf(membershipStr)
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
membershipStr = value.name
|
||||||
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,9 +23,9 @@ import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun GroupEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<GroupEntity> {
|
internal fun GroupEntity.Companion.where(realm: Realm, groupId: String): RealmQuery<GroupEntity> {
|
||||||
return realm.where<GroupEntity>()
|
return realm.where<GroupEntity>()
|
||||||
.equalTo(GroupEntityFields.GROUP_ID, roomId)
|
.equalTo(GroupEntityFields.GROUP_ID, groupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> {
|
internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> {
|
||||||
|
|
|
@ -30,3 +30,7 @@ internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupId: String? =
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupIds: List<String>): RealmQuery<GroupSummaryEntity> {
|
||||||
|
return realm.where<GroupSummaryEntity>()
|
||||||
|
.`in`(GroupSummaryEntityFields.GROUP_ID, groupIds.toTypedArray())
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.session.group
|
package im.vector.matrix.android.internal.session.group
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
@ -64,8 +65,7 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
||||||
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
|
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
|
||||||
val name = groupSummary.profile?.name
|
val name = groupSummary.profile?.name
|
||||||
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
|
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
|
||||||
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription
|
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""
|
||||||
?: ""
|
|
||||||
|
|
||||||
val roomIds = groupRooms.rooms.map { it.roomId }
|
val roomIds = groupRooms.rooms.map { it.roomId }
|
||||||
groupSummaryEntity.roomIds.clear()
|
groupSummaryEntity.roomIds.clear()
|
||||||
|
@ -74,8 +74,12 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
||||||
val userIds = groupUsers.users.map { it.userId }
|
val userIds = groupUsers.users.map { it.userId }
|
||||||
groupSummaryEntity.userIds.clear()
|
groupSummaryEntity.userIds.clear()
|
||||||
groupSummaryEntity.userIds.addAll(userIds)
|
groupSummaryEntity.userIds.addAll(userIds)
|
||||||
|
|
||||||
|
groupSummaryEntity.membership = when (groupSummary.user?.membership) {
|
||||||
|
Membership.JOIN.value -> Membership.JOIN
|
||||||
|
Membership.INVITE.value -> Membership.INVITE
|
||||||
|
else -> Membership.LEAVE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,15 +21,15 @@ import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.model.GroupEntity
|
import im.vector.matrix.android.internal.database.model.GroupEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -37,18 +37,30 @@ private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
|
||||||
|
|
||||||
internal class GroupSummaryUpdater @Inject constructor(private val context: Context,
|
internal class GroupSummaryUpdater @Inject constructor(private val context: Context,
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
@SessionDatabase realmConfiguration: RealmConfiguration)
|
private val monarchy: Monarchy)
|
||||||
: RealmLiveEntityObserver<GroupEntity>(realmConfiguration) {
|
: RealmLiveEntityObserver<GroupEntity>(monarchy.realmConfiguration) {
|
||||||
|
|
||||||
override val query = Monarchy.Query<GroupEntity> { GroupEntity.where(it) }
|
override val query = Monarchy.Query { GroupEntity.where(it) }
|
||||||
|
|
||||||
override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) {
|
override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) {
|
||||||
val newGroupIds = changeSet.insertions
|
// `insertions` for new groups and `changes` to handle left groups
|
||||||
|
val modifiedGroupEntity = (changeSet.insertions + changeSet.changes)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.mapNotNull { results[it]?.groupId}
|
.mapNotNull { results[it] }
|
||||||
.toList()
|
|
||||||
|
|
||||||
val getGroupDataWorkerParams = GetGroupDataWorker.Params(credentials.userId, newGroupIds)
|
fetchGroupsData(modifiedGroupEntity
|
||||||
|
.filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
|
||||||
|
.map { it.groupId }
|
||||||
|
.toList())
|
||||||
|
|
||||||
|
deleteGroups(modifiedGroupEntity
|
||||||
|
.filter { it.membership == Membership.LEAVE }
|
||||||
|
.map { it.groupId }
|
||||||
|
.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchGroupsData(groupIds: List<String>) {
|
||||||
|
val getGroupDataWorkerParams = GetGroupDataWorker.Params(credentials.userId, groupIds)
|
||||||
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
|
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
|
||||||
|
|
||||||
val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
|
val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
|
||||||
|
@ -61,4 +73,15 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
|
||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the GroupSummaryEntity of left groups
|
||||||
|
*/
|
||||||
|
private fun deleteGroups(groupIds: List<String>) {
|
||||||
|
monarchy
|
||||||
|
.writeAsync { realm ->
|
||||||
|
GroupSummaryEntity.where(realm, groupIds)
|
||||||
|
.findAll()
|
||||||
|
.deleteAllFromRealm()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -64,12 +64,13 @@ internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarc
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Note: [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] is observing changes */
|
||||||
realm.insertOrUpdate(groups)
|
realm.insertOrUpdate(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleJoinedGroup(realm: Realm,
|
private fun handleJoinedGroup(realm: Realm,
|
||||||
groupId: String): GroupEntity {
|
groupId: String): GroupEntity {
|
||||||
|
|
||||||
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
|
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
|
||||||
groupEntity.membership = Membership.JOIN
|
groupEntity.membership = Membership.JOIN
|
||||||
return groupEntity
|
return groupEntity
|
||||||
|
@ -77,21 +78,16 @@ internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarc
|
||||||
|
|
||||||
private fun handleInvitedGroup(realm: Realm,
|
private fun handleInvitedGroup(realm: Realm,
|
||||||
groupId: String): GroupEntity {
|
groupId: String): GroupEntity {
|
||||||
|
|
||||||
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
|
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
|
||||||
groupEntity.membership = Membership.INVITE
|
groupEntity.membership = Membership.INVITE
|
||||||
return groupEntity
|
return groupEntity
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO : handle it
|
|
||||||
private fun handleLeftGroup(realm: Realm,
|
private fun handleLeftGroup(realm: Realm,
|
||||||
groupId: String): GroupEntity {
|
groupId: String): GroupEntity {
|
||||||
|
|
||||||
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
|
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
|
||||||
groupEntity.membership = Membership.LEAVE
|
groupEntity.membership = Membership.LEAVE
|
||||||
return groupEntity
|
return groupEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,11 +21,19 @@ import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncService
|
import im.vector.matrix.android.internal.session.sync.job.SyncService
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.vectorComponent
|
||||||
import im.vector.riotx.features.notifications.NotificationUtils
|
import im.vector.riotx.features.notifications.NotificationUtils
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class VectorSyncService : SyncService() {
|
class VectorSyncService : SyncService() {
|
||||||
|
|
||||||
|
private lateinit var notificationUtils: NotificationUtils
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
notificationUtils = vectorComponent().notificationUtils()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
removeForegroundNotif()
|
removeForegroundNotif()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
@ -43,7 +51,7 @@ class VectorSyncService : SyncService() {
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
Timber.v("VectorSyncService - onStartCommand ")
|
Timber.v("VectorSyncService - onStartCommand ")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false)
|
val notification = notificationUtils.buildForegroundServiceNotification(R.string.notification_listening_for_events, false)
|
||||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||||
}
|
}
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.postLiveEvent
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
|
@ -93,20 +94,20 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
.liveGroupSummaries()
|
.liveGroupSummaries()
|
||||||
|
// Keep only joined groups. Group invitations will be managed later
|
||||||
|
.map { it.filter { groupSummary -> groupSummary.membership == Membership.JOIN } }
|
||||||
.map {
|
.map {
|
||||||
val myUser = session.getUser(session.myUserId)
|
val myUser = session.getUser(session.myUserId)
|
||||||
val allCommunityGroup = GroupSummary(
|
val allCommunityGroup = GroupSummary(
|
||||||
groupId = ALL_COMMUNITIES_GROUP_ID,
|
groupId = ALL_COMMUNITIES_GROUP_ID,
|
||||||
|
membership = Membership.JOIN,
|
||||||
displayName = stringProvider.getString(R.string.group_all_communities),
|
displayName = stringProvider.getString(R.string.group_all_communities),
|
||||||
avatarUrl = myUser?.avatarUrl ?: "")
|
avatarUrl = myUser?.avatarUrl ?: "")
|
||||||
listOf(allCommunityGroup) + it
|
listOf(allCommunityGroup) + it
|
||||||
}
|
}
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
// TODO Phase2 Handle the case where the selected group is deleted on another client
|
|
||||||
val newSelectedGroup = selectedGroup ?: async()?.firstOrNull()
|
val newSelectedGroup = selectedGroup ?: async()?.firstOrNull()
|
||||||
copy(asyncGroups = async, selectedGroup = newSelectedGroup)
|
copy(asyncGroups = async, selectedGroup = newSelectedGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue