Merge pull request #570 from vector-im/feature/left_group

Handle left group from sync
This commit is contained in:
Benoit Marty 2019-09-20 17:44:13 +02:00 committed by GitHub
commit 5651ea515b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 95 additions and 40 deletions

View file

@ -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:
- -

View file

@ -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 = "",

View file

@ -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()
) )
} }
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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> {

View file

@ -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())
}

View file

@ -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
}
} }
} }
} }

View file

@ -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()
}
}
} }

View file

@ -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
} }
} }

View file

@ -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)

View file

@ -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)
} }
} }
} }