legacy groups removal (#6268)

This commit is contained in:
Nikita Fedrunov 2022-07-15 12:25:10 +02:00 committed by GitHub
parent 92801f625d
commit c7b54b8d3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 751 additions and 3039 deletions

1
changelog.d/5733.misc Normal file
View file

@ -0,0 +1 @@
Communities/Groups are removed completely

1
changelog.d/5733.sdk Normal file
View file

@ -0,0 +1 @@
Communities/Groups are removed completely

View file

@ -26,8 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.api.session.room.RoomSortOrder
@ -59,13 +57,6 @@ class FlowSession(private val session: Session) {
}
}
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow<List<GroupSummary>> {
return session.groupService().getGroupSummariesLive(queryParams).asFlow()
.startWith(session.coroutineDispatchers.io) {
session.groupService().getGroupSummaries(queryParams)
}
}
fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow<List<RoomSummary>> {
return session.spaceService().getSpaceSummariesLive(queryParams).asFlow()
.startWith(session.coroutineDispatchers.io) {

View file

@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
@ -154,11 +153,6 @@ interface Session {
*/
fun roomDirectoryService(): RoomDirectoryService
/**
* Returns the GroupService associated with the session.
*/
fun groupService(): GroupService
/**
* Returns the UserService associated with the session.
*/

View file

@ -1,31 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.group
/**
* This interface defines methods to interact within a group.
*/
interface Group {
val groupId: String
/**
* This methods allows you to refresh data about this group. It will be reflected on the GroupSummary.
* The SDK also takes care of refreshing group data every hour.
* @return a Cancelable to be able to cancel requests.
*/
suspend fun fetchGroupData()
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.group
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.group.model.GroupSummary
/**
* This interface defines methods to get groups. It's implemented at the session level.
*/
interface GroupService {
/**
* Get a group from a groupId.
* @param groupId the groupId to look for.
* @return the group with groupId or null
*/
fun getGroup(groupId: String): Group?
/**
* Get a groupSummary from a groupId.
* @param groupId the groupId to look for.
* @return the groupSummary with groupId or null
*/
fun getGroupSummary(groupId: String): GroupSummary?
/**
* Get a list of group summaries. This list is a snapshot of the data.
* @return the list of [GroupSummary]
*/
fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary>
/**
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [GroupSummary]
*/
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>>
}

View file

@ -1,44 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.group
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.room.model.Membership
fun groupSummaryQueryParams(init: (GroupSummaryQueryParams.Builder.() -> Unit) = {}): GroupSummaryQueryParams {
return GroupSummaryQueryParams.Builder().apply(init).build()
}
/**
* This class can be used to filter group summaries.
*/
data class GroupSummaryQueryParams(
val displayName: QueryStringValue,
val memberships: List<Membership>
) {
class Builder {
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List<Membership> = Membership.all()
fun build() = GroupSummaryQueryParams(
displayName = displayName,
memberships = memberships
)
}
}

View file

@ -1,33 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.group.model
import org.matrix.android.sdk.api.session.room.model.Membership
/**
* This class holds some data of a group.
* It can be retrieved through [org.matrix.android.sdk.api.session.group.GroupService]
*/
data class GroupSummary(
val groupId: String,
val membership: Membership,
val displayName: String = "",
val shortDescription: String = "",
val avatarUrl: String = "",
val roomIds: List<String> = emptyList(),
val userIds: List<String> = emptyList()
)

View file

@ -54,7 +54,5 @@ sealed class PermalinkData {
data class UserLink(val userId: String) : PermalinkData()
data class GroupLink(val groupId: String) : PermalinkData()
data class FallbackLink(val uri: Uri) : PermalinkData()
data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData()
}

View file

@ -61,27 +61,29 @@ object PermalinkParser {
val params = safeFragment
.split(MatrixPatterns.SEP_REGEX)
.filter { it.isNotEmpty() }
.map { URLDecoder.decode(it, "UTF-8") }
.take(2)
val decodedParams = params
.map { URLDecoder.decode(it, "UTF-8") }
val identifier = params.getOrNull(0)
val extraParameter = params.getOrNull(1)
val decodedIdentifier = decodedParams.getOrNull(0)
val extraParameter = decodedParams.getOrNull(1)
return when {
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
MatrixPatterns.isRoomId(identifier) -> {
handleRoomIdCase(fragment, identifier, matrixToUri, extraParameter, viaQueryParameters)
identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier)
MatrixPatterns.isRoomId(decodedIdentifier) -> {
handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters)
}
MatrixPatterns.isRoomAlias(identifier) -> {
MatrixPatterns.isRoomAlias(decodedIdentifier) -> {
PermalinkData.RoomLink(
roomIdOrAlias = identifier,
roomIdOrAlias = decodedIdentifier,
isRoomAlias = true,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters
)
}
else -> PermalinkData.FallbackLink(uri)
else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier))
}
}

View file

@ -87,10 +87,6 @@ data class RoomSummaryQueryParams(
* Used to filter room using the current space.
*/
val spaceFilter: SpaceFilter?,
/**
* Used to filter room using the current group.
*/
val activeGroupId: String? = null
) {
/**
@ -106,7 +102,6 @@ data class RoomSummaryQueryParams(
var excludeType: List<String?>? = listOf(RoomType.SPACE)
var includeType: List<String?>? = null
var spaceFilter: SpaceFilter? = null
var activeGroupId: String? = null
fun build() = RoomSummaryQueryParams(
displayName = displayName,
@ -117,7 +112,6 @@ data class RoomSummaryQueryParams(
excludeType = excludeType,
includeType = includeType,
spaceFilter = spaceFilter,
activeGroupId = activeGroupId
)
}
}

View file

@ -22,7 +22,6 @@ enum class InitialSyncStep {
ImportingAccount,
ImportingAccountCrypto,
ImportingAccountRoom,
ImportingAccountGroups,
ImportingAccountData,
ImportingAccountJoinedRooms,
ImportingAccountInvitedRooms,

View file

@ -1,33 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.sync.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class GroupSyncProfile(
/**
* The name of the group, if any. May be nil.
*/
@Json(name = "name") val name: String? = null,
/**
* The URL for the group's avatar. May be nil.
*/
@Json(name = "avatar_url") val avatarUrl: String? = null
)

View file

@ -1,38 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.sync.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class GroupsSyncResponse(
/**
* Joined groups: An array of groups ids.
*/
@Json(name = "join") val join: Map<String, Any> = emptyMap(),
/**
* Invitations. The groups that the user has been invited to: keys are groups ids.
*/
@Json(name = "invite") val invite: Map<String, InvitedGroupSync> = emptyMap(),
/**
* Left groups. An array of groups ids: the groups that the user has left or been banned from.
*/
@Json(name = "leave") val leave: Map<String, Any> = emptyMap()
)

View file

@ -1,33 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.sync.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class InvitedGroupSync(
/**
* The identifier of the inviter.
*/
@Json(name = "inviter") val inviter: String? = null,
/**
* The group profile.
*/
@Json(name = "profile") val profile: GroupSyncProfile? = null
)

View file

@ -65,10 +65,4 @@ data class SyncResponse(
*/
@Json(name = "org.matrix.msc2732.device_unused_fallback_key_types")
val deviceUnusedFallbackKeyTypes: List<String>? = null,
/**
* List of groups.
*/
@Json(name = "groups") val groups: GroupsSyncResponse? = null
)

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.util
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
@ -113,19 +112,6 @@ sealed class MatrixItem(
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
data class GroupItem(
override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null
) :
MatrixItem(id, displayName, avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
protected fun checkId() {
if (!id.startsWith(getIdPrefix())) {
error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}")
@ -144,7 +130,6 @@ sealed class MatrixItem(
is RoomItem,
is EveryoneInRoomItem -> '!'
is RoomAliasItem -> '#'
is GroupItem -> '+'
}
fun firstLetterOfDisplayName(): String {
@ -196,8 +181,6 @@ sealed class MatrixItem(
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
MatrixItem.SpaceItem(roomId, displayName, avatarUrl)
} else {

View file

@ -49,6 +49,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber
import javax.inject.Inject
@ -63,7 +64,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000
val schemaVersion = 31L
val schemaVersion = 32L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@ -99,5 +100,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 29) MigrateSessionTo029(realm).perform()
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
if (oldVersion < 32) MigrateSessionTo032(realm).perform()
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
internal object GroupSummaryMapper {
fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
return GroupSummary(
groupSummaryEntity.groupId,
groupSummaryEntity.membership,
groupSummaryEntity.displayName,
groupSummaryEntity.shortDescription,
groupSummaryEntity.avatarUrl,
groupSummaryEntity.roomIds.toList(),
groupSummaryEntity.userIds.toList()
)
}
}
internal fun GroupSummaryEntity.asDomain(): GroupSummary {
return GroupSummaryMapper.map(this)
}

View file

@ -49,7 +49,7 @@ internal class MigrateSessionTo010(realm: DynamicRealm) : RealmMigrator(realm, 1
realm.schema.get("RoomSummaryEntity")
?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
?.addField("groupIds", String::class.java)
?.transform { obj ->
val creationEvent = realm.where("CurrentStateEventEntity")

View file

@ -1,11 +1,11 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,15 +14,15 @@
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
package org.matrix.android.sdk.internal.database.migration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
@JsonClass(generateAdapter = true)
internal data class GroupRooms(
internal class MigrateSessionTo032(realm: DynamicRealm) : RealmMigrator(realm, 32) {
@Json(name = "total_room_count_estimate") val totalRoomCountEstimate: Int? = null,
@Json(name = "chunk") val rooms: List<GroupRoom> = emptyList()
)
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("RoomSummaryEntity")
?.removeField("groupIds")
}
}

View file

@ -1,40 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.api.session.room.model.Membership
/**
* This class is used to store group info (groupId and membership) from the sync response.
* Then GetGroupDataTask is called regularly to fetch group information from the homeserver.
*/
internal open class GroupEntity(@PrimaryKey var groupId: String = "") :
RealmObject() {
private var membershipStr: String = Membership.NONE.name
var membership: Membership
get() {
return Membership.valueOf(membershipStr)
}
set(value) {
membershipStr = value.name
}
companion object
}

View file

@ -1,43 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.model
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.api.session.room.model.Membership
internal open class GroupSummaryEntity(
@PrimaryKey var groupId: String = "",
var displayName: String = "",
var shortDescription: String = "",
var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList(),
var userIds: RealmList<String> = RealmList()
) : RealmObject() {
private var membershipStr: String = Membership.NONE.name
var membership: Membership
get() {
return Membership.valueOf(membershipStr)
}
set(value) {
membershipStr = value.name
}
companion object
}

View file

@ -240,11 +240,6 @@ internal open class RoomSummaryEntity(
if (value != field) field = value
}
var groupIds: String? = null
set(value) {
if (value != field) field = value
}
@Index
private var membershipStr: String = Membership.NONE.name

View file

@ -32,8 +32,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
EventInsertEntity::class,
TimelineEventEntity::class,
FilterEntity::class,
GroupEntity::class,
GroupSummaryEntity::class,
ReadReceiptEntity::class,
RoomEntity::class,
RoomSummaryEntity::class,

View file

@ -1,34 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.query
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.model.GroupEntity
import org.matrix.android.sdk.internal.database.model.GroupEntityFields
import org.matrix.android.sdk.internal.query.process
internal fun GroupEntity.Companion.where(realm: Realm, groupId: String): RealmQuery<GroupEntity> {
return realm.where<GroupEntity>()
.equalTo(GroupEntityFields.GROUP_ID, groupId)
}
internal fun GroupEntity.Companion.where(realm: Realm, memberships: List<Membership>): RealmQuery<GroupEntity> {
return realm.where<GroupEntity>().process(GroupEntityFields.MEMBERSHIP_STR, memberships)
}

View file

@ -1,41 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.query
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.createObject
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields
internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupId: String? = null): RealmQuery<GroupSummaryEntity> {
val query = realm.where<GroupSummaryEntity>()
if (groupId != null) {
query.equalTo(GroupSummaryEntityFields.GROUP_ID, groupId)
}
return query
}
internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupIds: List<String>): RealmQuery<GroupSummaryEntity> {
return realm.where<GroupSummaryEntity>()
.`in`(GroupSummaryEntityFields.GROUP_ID, groupIds.toTypedArray())
}
internal fun GroupSummaryEntity.Companion.getOrCreate(realm: Realm, groupId: String): GroupSummaryEntity {
return where(realm, groupId).findFirst() ?: realm.createObject(groupId)
}

View file

@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
@ -97,7 +96,6 @@ internal class DefaultSession @Inject constructor(
private val sessionListeners: SessionListeners,
private val roomService: Lazy<RoomService>,
private val roomDirectoryService: Lazy<RoomDirectoryService>,
private val groupService: Lazy<GroupService>,
private val userService: Lazy<UserService>,
private val filterService: Lazy<FilterService>,
private val federationService: Lazy<FederationService>,
@ -209,7 +207,6 @@ internal class DefaultSession @Inject constructor(
override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get()
override fun roomService(): RoomService = roomService.get()
override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
override fun groupService(): GroupService = groupService.get()
override fun userService(): UserService = userService.get()
override fun signOutService(): SignOutService = signOutService.get()
override fun filterService(): FilterService = filterService.get()

View file

@ -35,8 +35,6 @@ import org.matrix.android.sdk.internal.session.content.ContentModule
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerModule
import org.matrix.android.sdk.internal.session.filter.FilterModule
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.group.GroupModule
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesModule
import org.matrix.android.sdk.internal.session.identity.IdentityModule
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule
@ -74,10 +72,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
SyncModule::class,
HomeServerCapabilitiesModule::class,
SignOutModule::class,
GroupModule::class,
UserModule::class,
FilterModule::class,
GroupModule::class,
ContentModule::class,
CacheModule::class,
MediaModule::class,
@ -124,8 +120,6 @@ internal interface SessionComponent {
fun inject(worker: RedactEventWorker)
fun inject(worker: GetGroupDataWorker)
fun inject(worker: UploadContentWorker)
fun inject(worker: SyncWorker)

View file

@ -25,7 +25,7 @@ internal class DisplayNameResolver @Inject constructor(
private val matrixConfiguration: MatrixConfiguration
) {
fun getBestName(matrixItem: MatrixItem): String {
return if (matrixItem is MatrixItem.GroupItem || matrixItem is MatrixItem.RoomAliasItem) {
return if (matrixItem is MatrixItem.RoomAliasItem) {
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
matrixItem.id
} else {

View file

@ -1,30 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import org.matrix.android.sdk.api.session.group.Group
internal class DefaultGroup(
override val groupId: String,
private val getGroupDataTask: GetGroupDataTask
) : Group {
override suspend fun fetchGroupData() {
val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId))
getGroupDataTask.execute(params)
}
}

View file

@ -1,80 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmQuery
import org.matrix.android.sdk.api.session.group.Group
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.GroupEntity
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.util.fetchCopyMap
import javax.inject.Inject
internal class DefaultGroupService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val groupFactory: GroupFactory,
private val queryStringValueProcessor: QueryStringValueProcessor,
) : GroupService {
override fun getGroup(groupId: String): Group? {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
GroupEntity.where(realm, groupId).findFirst()?.let {
groupFactory.create(groupId)
}
}
}
override fun getGroupSummary(groupId: String): GroupSummary? {
return monarchy.fetchCopyMap(
{ realm -> GroupSummaryEntity.where(realm, groupId).findFirst() },
{ it, _ -> it.asDomain() }
)
}
override fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary> {
return monarchy.fetchAllMappedSync(
{ groupSummariesQuery(it, groupSummaryQueryParams) },
{ it.asDomain() }
)
}
override fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>> {
return monarchy.findAllMappedWithChanges(
{ groupSummariesQuery(it, groupSummaryQueryParams) },
{ it.asDomain() }
)
}
private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> {
return with(queryStringValueProcessor) {
GroupSummaryEntity.where(realm)
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
}
}
}

View file

@ -1,110 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.model.GroupEntity
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.group.model.GroupRooms
import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse
import org.matrix.android.sdk.internal.session.group.model.GroupUsers
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
import timber.log.Timber
import javax.inject.Inject
internal interface GetGroupDataTask : Task<GetGroupDataTask.Params, Unit> {
sealed class Params {
object FetchAllActive : Params()
data class FetchWithIds(val groupIds: List<String>) : Params()
}
}
internal class DefaultGetGroupDataTask @Inject constructor(
private val groupAPI: GroupAPI,
@SessionDatabase private val monarchy: Monarchy,
private val globalErrorReceiver: GlobalErrorReceiver
) : GetGroupDataTask {
private data class GroupData(
val groupId: String,
val groupSummary: GroupSummaryResponse,
val groupRooms: GroupRooms,
val groupUsers: GroupUsers
)
override suspend fun execute(params: GetGroupDataTask.Params) {
val groupIds = when (params) {
is GetGroupDataTask.Params.FetchAllActive -> {
getActiveGroupIds()
}
is GetGroupDataTask.Params.FetchWithIds -> {
params.groupIds
}
}
Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}")
val data = groupIds.map { groupId ->
val groupSummary = executeRequest(globalErrorReceiver) {
groupAPI.getSummary(groupId)
}
val groupRooms = executeRequest(globalErrorReceiver) {
groupAPI.getRooms(groupId)
}
val groupUsers = executeRequest(globalErrorReceiver) {
groupAPI.getUsers(groupId)
}
GroupData(groupId, groupSummary, groupRooms, groupUsers)
}
insertInDb(data)
}
private fun getActiveGroupIds(): List<String> {
return monarchy.fetchAllMappedSync(
{ realm ->
GroupEntity.where(realm, Membership.activeMemberships())
},
{ it.groupId }
)
}
private suspend fun insertInDb(groupDataList: List<GroupData>) {
monarchy
.awaitTransaction { realm ->
groupDataList.forEach { groupData ->
val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupData.groupId)
groupSummaryEntity.avatarUrl = groupData.groupSummary.profile?.avatarUrl ?: ""
val name = groupData.groupSummary.profile?.name
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupData.groupId else name
groupSummaryEntity.shortDescription = groupData.groupSummary.profile?.shortDescription ?: ""
groupSummaryEntity.roomIds.clear()
groupData.groupRooms.rooms.mapTo(groupSummaryEntity.roomIds) { it.roomId }
groupSummaryEntity.userIds.clear()
groupData.groupUsers.users.mapTo(groupSummaryEntity.userIds) { it.userId }
}
}
}
}

View file

@ -1,59 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import android.content.Context
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject
/**
* Possible previous worker: None.
* Possible next worker : None.
*/
internal class GetGroupDataWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject lateinit var getGroupDataTask: GetGroupDataTask
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
return runCatching {
getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive)
}.fold(
{ Result.success() },
{ Result.retry() }
)
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.group.model.GroupRooms
import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse
import org.matrix.android.sdk.internal.session.group.model.GroupUsers
import retrofit2.http.GET
import retrofit2.http.Path
internal interface GroupAPI {
/**
* Request a group summary.
*
* @param groupId the group id
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary")
suspend fun getSummary(@Path("groupId") groupId: String): GroupSummaryResponse
/**
* Request the rooms list.
*
* @param groupId the group id
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms")
suspend fun getRooms(@Path("groupId") groupId: String): GroupRooms
/**
* Request the users list.
*
* @param groupId the group id
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users")
suspend fun getUsers(@Path("groupId") groupId: String): GroupUsers
}

View file

@ -1,37 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import org.matrix.android.sdk.api.session.group.Group
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
internal interface GroupFactory {
fun create(groupId: String): Group
}
@SessionScope
internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask) :
GroupFactory {
override fun create(groupId: String): Group {
return DefaultGroup(
groupId = groupId,
getGroupDataTask = getGroupDataTask
)
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group
import dagger.Binds
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.internal.session.SessionScope
import retrofit2.Retrofit
@Module
internal abstract class GroupModule {
@Module
companion object {
@Provides
@JvmStatic
@SessionScope
fun providesGroupAPI(retrofit: Retrofit): GroupAPI {
return retrofit.create(GroupAPI::class.java)
}
}
@Binds
abstract fun bindGroupFactory(factory: DefaultGroupFactory): GroupFactory
@Binds
abstract fun bindGetGroupDataTask(task: DefaultGetGroupDataTask): GetGroupDataTask
@Binds
abstract fun bindGroupService(service: DefaultGroupService): GroupService
}

View file

@ -1,49 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class represents a community profile in the server responses.
*/
@JsonClass(generateAdapter = true)
internal data class GroupProfile(
@Json(name = "short_description") val shortDescription: String? = null,
/**
* Tell whether the group is public.
*/
@Json(name = "is_public") val isPublic: Boolean? = null,
/**
* The URL for the group's avatar. May be nil.
*/
@Json(name = "avatar_url") val avatarUrl: String? = null,
/**
* The group's name.
*/
@Json(name = "name") val name: String? = null,
/**
* The optional HTML formatted string used to described the group.
*/
@Json(name = "long_description") val longDescription: String? = null
)

View file

@ -1,35 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class GroupRoom(
@Json(name = "aliases") val aliases: List<String> = emptyList(),
@Json(name = "canonical_alias") val canonicalAlias: String? = null,
@Json(name = "name") val name: String? = null,
@Json(name = "num_joined_members") val numJoinedMembers: Int = 0,
@Json(name = "room_id") val roomId: String,
@Json(name = "topic") val topic: String? = null,
@Json(name = "world_readable") val worldReadable: Boolean = false,
@Json(name = "guest_can_join") val guestCanJoin: Boolean = false,
@Json(name = "avatar_url") val avatarUrl: String? = null
)

View file

@ -1,46 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class represents the summary of a community in the server response.
*/
@JsonClass(generateAdapter = true)
internal data class GroupSummaryResponse(
/**
* The group profile.
*/
@Json(name = "profile") val profile: GroupProfile? = null,
/**
* The group users.
*/
@Json(name = "users_section") val usersSection: GroupSummaryUsersSection? = null,
/**
* The current user status.
*/
@Json(name = "user") val user: GroupSummaryUser? = null,
/**
* The rooms linked to the community.
*/
@Json(name = "rooms_section") val roomsSection: GroupSummaryRoomsSection? = null
)

View file

@ -1,34 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class represents the community rooms in a group summary response.
*/
@JsonClass(generateAdapter = true)
internal data class GroupSummaryRoomsSection(
@Json(name = "total_room_count_estimate") val totalRoomCountEstimate: Int? = null,
@Json(name = "rooms") val rooms: List<String> = emptyList()
// TODO Check the meaning and the usage of these categories. This dictionary is empty FTM.
// public Map<Object, Object> categories;
)

View file

@ -1,37 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class represents the current user status in a group summary response.
*/
@JsonClass(generateAdapter = true)
internal data class GroupSummaryUser(
/**
* The current user membership in this community.
*/
@Json(name = "membership") val membership: String? = null,
/**
* Tell whether the user published this community on his profile.
*/
@Json(name = "is_publicised") val isPublicised: Boolean? = null
)

View file

@ -1,35 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This class represents the community members in a group summary response.
*/
@JsonClass(generateAdapter = true)
internal data class GroupSummaryUsersSection(
@Json(name = "total_user_count_estimate") val totalUserCountEstimate: Int,
@Json(name = "users") val users: List<String> = emptyList()
// TODO Check the meaning and the usage of these roles. This dictionary is empty FTM.
// public Map<Object, Object> roles;
)

View file

@ -1,29 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class GroupUser(
@Json(name = "display_name") val displayName: String = "",
@Json(name = "user_id") val userId: String,
@Json(name = "is_privileged") val isPrivileged: Boolean = false,
@Json(name = "avatar_url") val avatarUrl: String? = "",
@Json(name = "is_public") val isPublic: Boolean = false
)

View file

@ -1,26 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.group.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class GroupUsers(
@Json(name = "total_user_count_estimate") val totalUserCountEstimate: Int,
@Json(name = "chunk") val users: List<GroupUser> = emptyList()
)

View file

@ -97,7 +97,6 @@ internal class PermalinkFactory @Inject constructor(
url.startsWith(MATRIX_TO_URL_BASE) -> url.substring(MATRIX_TO_URL_BASE.length)
clientBaseUrl != null && url.startsWith(clientBaseUrl) -> {
when (PermalinkParser.parse(url)) {
is PermalinkData.GroupLink -> url.substring(clientBaseUrl.length + GROUP_PATH.length)
is PermalinkData.RoomLink -> url.substring(clientBaseUrl.length + ROOM_PATH.length)
is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length)
else -> null

View file

@ -328,9 +328,6 @@ internal class RoomSummaryDataSource @Inject constructor(
null -> Unit // nop
}
queryParams.activeGroupId?.let { activeGroupId ->
query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId)
}
return query
}

View file

@ -44,7 +44,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningSe
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@ -438,38 +437,6 @@ internal class RoomSummaryUpdater @Inject constructor(
space.notificationCount = notificationCount
}
// xxx invites??
// LEGACY GROUPS
// lets mark rooms that belongs to groups
val existingGroups = GroupSummaryEntity.where(realm).findAll()
// For rooms
realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
.findAll().forEach { room ->
val belongsTo = existingGroups.filter { it.roomIds.contains(room.roomId) }
room.groupIds = if (belongsTo.isEmpty()) {
null
} else {
"|${belongsTo.joinToString("|")}|"
}
}
// For DMS
realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.findAll().forEach { room ->
val belongsTo = existingGroups.filter {
it.userIds.intersect(room.otherMemberIds).isNotEmpty()
}
room.groupIds = if (belongsTo.isEmpty()) {
null
} else {
"|${belongsTo.joinToString("|")}|"
}
}
}.also {
Timber.v("## SPACES: Finish checking room hierarchy in $it ms")
}

View file

@ -16,47 +16,36 @@
package org.matrix.android.sdk.internal.session.sync
import androidx.work.ExistingPeriodicWorkPolicy
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.pushrules.RuleScope
import org.matrix.android.sdk.api.session.sync.InitialSyncStep
import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionListeners
import org.matrix.android.sdk.internal.session.dispatchTo
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.system.measureTimeMillis
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class SyncResponseHandler @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
@SessionId private val sessionId: String,
private val sessionManager: SessionManager,
private val sessionListeners: SessionListeners,
private val workManagerProvider: WorkManagerProvider,
private val roomSyncHandler: RoomSyncHandler,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val groupSyncHandler: GroupSyncHandler,
private val cryptoSyncHandler: CryptoSyncHandler,
private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler,
private val cryptoService: DefaultCryptoService,
@ -109,7 +98,7 @@ internal class SyncResponseHandler @Inject constructor(
// IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local)
measureTimeMillis {
Timber.v("Handle rooms")
reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.7f) {
reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.8f) {
if (syncResponse.rooms != null) {
roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter)
}
@ -118,17 +107,6 @@ internal class SyncResponseHandler @Inject constructor(
Timber.v("Finish handling rooms in $it ms")
}
measureTimeMillis {
reportSubtask(reporter, InitialSyncStep.ImportingAccountGroups, 1, 0.1f) {
Timber.v("Handle groups")
if (syncResponse.groups != null) {
groupSyncHandler.handle(realm, syncResponse.groups, reporter)
}
}
}.also {
Timber.v("Finish handling groups in $it ms")
}
measureTimeMillis {
reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) {
Timber.v("Handle accountData")
@ -155,9 +133,6 @@ internal class SyncResponseHandler @Inject constructor(
userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
dispatchInvitedRoom(it)
}
syncResponse.groups?.let {
scheduleGroupDataFetchingIfNeeded(it)
}
Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse)
@ -177,31 +152,6 @@ internal class SyncResponseHandler @Inject constructor(
}
}
/**
* At the moment we don't get any group data through the sync, so we poll where every hour.
* You can also force to refetch group data using [Group] API.
*/
private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) {
val groupIds = ArrayList<String>()
groupIds.addAll(groupsSyncResponse.join.keys)
groupIds.addAll(groupsSyncResponse.invite.keys)
if (groupIds.isEmpty()) {
Timber.v("No new groups to fetch data for.")
return
}
Timber.v("There are ${groupIds.size} new groups to fetch data for.")
val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId)
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
val getGroupWork = workManagerProvider.matrixPeriodicWorkRequestBuilder<GetGroupDataWorker>(1, TimeUnit.HOURS)
.setInputData(workData)
.setConstraints(WorkManagerProvider.workConstraints)
.build()
workManagerProvider.workManager
.enqueueUniquePeriodicWork(GET_GROUP_DATA_WORKER, ExistingPeriodicWorkPolicy.REPLACE, getGroupWork)
}
private suspend fun checkPushRules(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean) {
Timber.v("[PushRules] --> checkPushRules")
if (isInitialSync) {

View file

@ -1,104 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.sync.handler
import io.realm.Realm
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.sync.InitialSyncStep
import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.InvitedGroupSync
import org.matrix.android.sdk.internal.database.model.GroupEntity
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
import org.matrix.android.sdk.internal.session.sync.mapWithProgress
import javax.inject.Inject
internal class GroupSyncHandler @Inject constructor() {
sealed class HandlingStrategy {
data class JOINED(val data: Map<String, Any>) : HandlingStrategy()
data class INVITED(val data: Map<String, InvitedGroupSync>) : HandlingStrategy()
data class LEFT(val data: Map<String, Any>) : HandlingStrategy()
}
fun handle(
realm: Realm,
roomsSyncResponse: GroupsSyncResponse,
reporter: ProgressReporter? = null
) {
handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter)
handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter)
handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter)
}
// PRIVATE METHODS *****************************************************************************
private fun handleGroupSync(realm: Realm, handlingStrategy: HandlingStrategy, reporter: ProgressReporter?) {
val groups = when (handlingStrategy) {
is HandlingStrategy.JOINED ->
handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.6f) {
handleJoinedGroup(realm, it.key)
}
is HandlingStrategy.INVITED ->
handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.3f) {
handleInvitedGroup(realm, it.key)
}
is HandlingStrategy.LEFT ->
handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.1f) {
handleLeftGroup(realm, it.key)
}
}
realm.insertOrUpdate(groups)
}
private fun handleJoinedGroup(
realm: Realm,
groupId: String
): GroupEntity {
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId)
groupEntity.membership = Membership.JOIN
groupSummaryEntity.membership = Membership.JOIN
return groupEntity
}
private fun handleInvitedGroup(
realm: Realm,
groupId: String
): GroupEntity {
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId)
groupEntity.membership = Membership.INVITE
groupSummaryEntity.membership = Membership.INVITE
return groupEntity
}
private fun handleLeftGroup(
realm: Realm,
groupId: String
): GroupEntity {
val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId)
groupEntity.membership = Membership.LEAVE
groupSummaryEntity.membership = Membership.LEAVE
return groupEntity
}
}

View file

@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
@ -53,8 +52,6 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
CheckFactoryWorker(appContext, workerParameters, true)
AddPusherWorker::class.java.name ->
AddPusherWorker(appContext, workerParameters, sessionManager)
GetGroupDataWorker::class.java.name ->
GetGroupDataWorker(appContext, workerParameters, sessionManager)
MultipleEventSendingDispatcherWorker::class.java.name ->
MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager)
RedactEventWorker::class.java.name ->

View file

@ -40,20 +40,11 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.sync.SyncRequestState
import javax.inject.Inject
import javax.inject.Singleton
sealed class RoomGroupingMethod {
data class ByLegacyGroup(val groupSummary: GroupSummary?) : RoomGroupingMethod()
data class BySpace(val spaceSummary: RoomSummary?) : RoomGroupingMethod()
}
fun RoomGroupingMethod.space() = (this as? RoomGroupingMethod.BySpace)?.spaceSummary
fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary
/**
* This class handles the global app state.
* It requires to be added to ProcessLifecycleOwner.get().lifecycle
@ -68,29 +59,20 @@ class AppStateHandler @Inject constructor(
) : DefaultLifecycleObserver {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomSummary>>(Option.empty())
val selectedRoomGroupingFlow = selectedSpaceDataSource.stream()
val selectedSpaceFlow = selectedSpaceDataSource.stream()
private val spaceBackstack = ArrayDeque<String?>()
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
// XXX we should somehow make it live :/ just a work around
// For example just after creating a space and switching to it the
// name in the app Bar could show Empty Room, and it will not update unless you
// switch space
return selectedSpaceDataSource.currentValue?.orNull()?.let {
if (it is RoomGroupingMethod.BySpace) {
// try to refresh sum?
it.spaceSummary?.roomId?.let { activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(it) }?.let {
RoomGroupingMethod.BySpace(it)
} ?: it
} else it
fun getCurrentSpace(): RoomSummary? {
return selectedSpaceDataSource.currentValue?.orNull()?.let { spaceSummary ->
activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(spaceSummary.roomId)
}
}
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false, isForwardNavigation: Boolean = true) {
val currentSpace = (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.space()
val currentSpace = selectedSpaceDataSource.currentValue?.orNull()
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
if (currentSpace != null && spaceId == currentSpace.roomId) return
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
@ -100,11 +82,15 @@ class AppStateHandler @Inject constructor(
}
if (persistNow) {
uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
}
selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.BySpace(spaceSum)))
if (spaceSum == null) {
selectedSpaceDataSource.post(Option.empty())
} else {
selectedSpaceDataSource.post(Option.just(spaceSum))
}
if (spaceId != null) {
uSession.coroutineScope.launch(Dispatchers.IO) {
tryOrNull {
@ -114,32 +100,13 @@ class AppStateHandler @Inject constructor(
}
}
fun setCurrentGroup(groupId: String?, session: Session? = null) {
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.ByLegacyGroup &&
groupId == selectedSpaceDataSource.currentValue?.orNull()?.group()?.groupId) return
val activeGroup = groupId?.let { uSession.groupService().getGroupSummary(groupId) }
selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.ByLegacyGroup(activeGroup)))
if (groupId != null) {
uSession.coroutineScope.launch {
tryOrNull {
uSession.groupService().getGroup(groupId)?.fetchGroupData()
}
}
}
}
private fun observeActiveSession() {
sessionDataSource.stream()
.distinctUntilChanged()
.onEach {
// sessionDataSource could already return a session while activeSession holder still returns null
it.orNull()?.let { session ->
if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) {
setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session)
} else {
setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session)
}
setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session)
observeSyncStatus(session)
}
}
@ -160,11 +127,7 @@ class AppStateHandler @Inject constructor(
fun getSpaceBackstack() = spaceBackstack
fun safeActiveSpaceId(): String? {
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId
}
fun safeActiveGroupId(): String? {
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
return selectedSpaceDataSource.currentValue?.orNull()?.roomId
}
override fun onResume(owner: LifecycleOwner) {
@ -174,15 +137,6 @@ class AppStateHandler @Inject constructor(
override fun onPause(owner: LifecycleOwner) {
coroutineScope.coroutineContext.cancelChildren()
val session = activeSessionHolder.getSafeActiveSession() ?: return
when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) {
is RoomGroupingMethod.BySpace -> {
uiStateRepository.storeGroupingMethod(true, session.sessionId)
uiStateRepository.storeSelectedSpace(currentMethod.spaceSummary?.roomId, session.sessionId)
}
is RoomGroupingMethod.ByLegacyGroup -> {
uiStateRepository.storeGroupingMethod(false, session.sessionId)
uiStateRepository.storeSelectedGroup(currentMethod.groupSummary?.groupId, session.sessionId)
}
}
uiStateRepository.storeSelectedSpace(selectedSpaceDataSource.currentValue?.orNull()?.roomId, session.sessionId)
}
}

View file

@ -16,19 +16,13 @@
package im.vector.app.features.analytics.extensions
import im.vector.app.RoomGroupingMethod
import im.vector.app.features.analytics.plan.ViewRoom
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
fun RoomSummary?.toAnalyticsViewRoom(trigger: ViewRoom.Trigger?, groupingMethod: RoomGroupingMethod? = null, viaKeyboard: Boolean? = null): ViewRoom {
val activeSpace = groupingMethod?.let {
when (it) {
is RoomGroupingMethod.BySpace -> it.spaceSummary?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home
else -> null
}
}
fun RoomSummary?.toAnalyticsViewRoom(trigger: ViewRoom.Trigger?, selectedSpace: RoomSummary? = null, viaKeyboard: Boolean? = null): ViewRoom {
val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home
return ViewRoom(
isDM = this?.isDirect.orFalse(),

View file

@ -1,47 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.autocomplete.group
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.features.autocomplete.AutocompleteClickListener
import im.vector.app.features.autocomplete.autocompleteMatrixItem
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class AutocompleteGroupController @Inject constructor() : TypedEpoxyController<List<GroupSummary>>() {
var listener: AutocompleteClickListener<GroupSummary>? = null
@Inject lateinit var avatarRenderer: AvatarRenderer
override fun buildModels(data: List<GroupSummary>?) {
if (data.isNullOrEmpty()) {
return
}
val host = this
data.forEach { groupSummary ->
autocompleteMatrixItem {
id(groupSummary.groupId)
matrixItem(groupSummary.toMatrixItem())
avatarRenderer(host.avatarRenderer)
clickListener { host.listener?.onItemClick(groupSummary) }
}
}
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.autocomplete.group
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import im.vector.app.features.autocomplete.AutocompleteClickListener
import im.vector.app.features.autocomplete.RecyclerViewPresenter
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import javax.inject.Inject
class AutocompleteGroupPresenter @Inject constructor(
context: Context,
private val controller: AutocompleteGroupController,
private val session: Session
) : RecyclerViewPresenter<GroupSummary>(context), AutocompleteClickListener<GroupSummary> {
init {
controller.listener = this
}
fun clear() {
controller.listener = null
}
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
return controller.adapter
}
override fun onItemClick(t: GroupSummary) {
dispatchClick(t)
}
override fun onQuery(query: CharSequence?) {
val queryParams = groupSummaryQueryParams {
displayName = if (query.isNullOrBlank()) {
QueryStringValue.IsNotEmpty
} else {
QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
}
}
val groups = session.groupService().getGroupSummaries(queryParams)
.asSequence()
.sortedBy { it.displayName }
controller.setData(groups.toList())
}
}

View file

@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.util.MatrixItem
fun MatrixItem.getBestName(): String {
// Note: this code is copied from [DisplayNameResolver] in the SDK
return if (this is MatrixItem.GroupItem || this is MatrixItem.RoomAliasItem) {
return if (this is MatrixItem.RoomAliasItem) {
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
id
} else {

View file

@ -1,54 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.app.features.grouplist
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass
abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>(R.layout.item_group) {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.onClick(listener)
holder.groupNameView.text = matrixItem.displayName
holder.rootView.isChecked = selected
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
}
}

View file

@ -214,13 +214,12 @@ class HomeActivity :
when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> openGroup(sharedAction.shouldClearFragment)
is HomeActivitySharedAction.OpenSpacePreview -> startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
is HomeActivitySharedAction.AddSpace -> createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
HomeActivitySharedAction.CloseGroup -> closeGroup()
HomeActivitySharedAction.OnCloseSpace -> onCloseSpace()
}
}
.launchIn(lifecycleScope)
@ -265,17 +264,6 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
}
private fun openGroup(shouldClearFragment: Boolean) {
views.drawerLayout.closeDrawer(GravityCompat.START)
// When switching from space to group or group to space, we need to reload the fragment
if (shouldClearFragment) {
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
} else {
// do nothing
}
}
private fun showSpaceSettings(spaceId: String) {
// open bottom sheet
SpaceSettingsMenuBottomSheet
@ -292,7 +280,7 @@ class HomeActivity :
.show(supportFragmentManager, "SPACE_INVITE")
}
private fun closeGroup() {
private fun onCloseSpace() {
views.drawerLayout.openDrawer(GravityCompat.START)
}

View file

@ -24,8 +24,7 @@ import im.vector.app.core.platform.VectorSharedAction
sealed class HomeActivitySharedAction : VectorSharedAction {
object OpenDrawer : HomeActivitySharedAction()
object CloseDrawer : HomeActivitySharedAction()
data class OpenGroup(val shouldClearFragment: Boolean) : HomeActivitySharedAction()
object CloseGroup : HomeActivitySharedAction()
object OnCloseSpace : HomeActivitySharedAction()
object AddSpace : HomeActivitySharedAction()
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
data class OpenSpaceInvite(val spaceId: String) : HomeActivitySharedAction()

View file

@ -30,7 +30,6 @@ import com.airbnb.mvrx.withState
import com.google.android.material.badge.BadgeDrawable
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed
@ -58,7 +57,6 @@ import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
@ -130,11 +128,8 @@ class HomeDetailFragment @Inject constructor(
views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId()
}
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
when (roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
}
viewModel.onEach(HomeDetailViewState::selectedSpace) { selectedSpace ->
onSpaceChange(selectedSpace)
}
viewModel.onEach(HomeDetailViewState::currentTab) { currentTab ->
@ -188,26 +183,16 @@ class HomeDetailFragment @Inject constructor(
}
private fun navigateBack() {
try {
val lastSpace = appStateHandler.getSpaceBackstack().removeLast()
setCurrentSpace(lastSpace)
} catch (e: NoSuchElementException) {
navigateUpOneSpace()
}
val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
setCurrentSpace(previousSpaceId ?: parentSpaceId)
}
private fun setCurrentSpace(spaceId: String?) {
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
sharedActionViewModel.post(HomeActivitySharedAction.CloseGroup)
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
}
private fun navigateUpOneSpace() {
val parentId = getCurrentSpace()?.flattenParentIds?.lastOrNull()
setCurrentSpace(parentId)
}
private fun getCurrentSpace() = (appStateHandler.getCurrentRoomGroupingMethod() as? RoomGroupingMethod.BySpace)?.spaceSummary
private fun handleCallStarted() {
dismissLoadingDialog()
val fragmentTag = HomeTab.DialPad.toFragmentTag()
@ -227,10 +212,8 @@ class HomeDetailFragment @Inject constructor(
}
private fun refreshSpaceState() {
when (val roomGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
else -> Unit
appStateHandler.getCurrentSpace()?.let {
onSpaceChange(it)
}
}
@ -291,15 +274,6 @@ class HomeDetailFragment @Inject constructor(
)
}
private fun onGroupChange(groupSummary: GroupSummary?) {
if (groupSummary == null) {
views.groupToolbarSpaceTitleView.isVisible = false
} else {
views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = groupSummary.displayName
}
}
private fun onSpaceChange(spaceSummary: RoomSummary?) {
if (spaceSummary == null) {
views.groupToolbarSpaceTitleView.isVisible = false
@ -335,16 +309,9 @@ class HomeDetailFragment @Inject constructor(
}
views.homeToolbarContent.debouncedClicks {
withState(viewModel) {
when (it.roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> {
// do nothing
}
is RoomGroupingMethod.BySpace -> {
it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
}
}
withState(viewModel) { viewState ->
viewState.selectedSpace?.let {
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId))
}
}
}
@ -499,7 +466,7 @@ class HomeDetailFragment @Inject constructor(
return this
}
override fun onBackPressed(toolbarButton: Boolean) = if (getCurrentSpace() != null) {
override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) {
navigateBack()
true
} else {

View file

@ -23,7 +23,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.singletonEntryPoint
@ -208,16 +207,16 @@ class HomeDetailViewModel @AssistedInject constructor(
}
private fun observeRoomGroupingMethod() {
appStateHandler.selectedRoomGroupingFlow
appStateHandler.selectedSpaceFlow
.setOnEach {
copy(
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
selectedSpace = it.orNull()
)
}
}
private fun observeRoomSummaries() {
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest {
appStateHandler.selectedSpaceFlow.distinctUntilChanged().flatMapLatest {
// we use it as a trigger to all changes in room, but do not really load
// the actual models
session.roomService().getPagedRoomSummariesLive(
@ -229,67 +228,55 @@ class HomeDetailViewModel @AssistedInject constructor(
}
.throttleFirst(300)
.onEach {
when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> {
// TODO!!
}
is RoomGroupingMethod.BySpace -> {
val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId
var dmInvites = 0
var roomsInvite = 0
if (autoAcceptInvites.showInvites()) {
dmInvites = session.roomService().getRoomSummaries(
roomSummaryQueryParams {
memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) }
}
).size
val activeSpaceRoomId = appStateHandler.getCurrentSpace()?.roomId
var dmInvites = 0
var roomsInvite = 0
if (autoAcceptInvites.showInvites()) {
dmInvites = session.roomService().getRoomSummaries(
roomSummaryQueryParams {
memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) }
}
).size
roomsInvite = session.roomService().getRoomSummaries(
roomSummaryQueryParams {
memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
spaceFilter = groupingMethod.toActiveSpaceOrOrphanRooms()
}
).size
roomsInvite = session.roomService().getRoomSummaries(
roomSummaryQueryParams {
memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
spaceFilter = activeSpaceRoomId.toActiveSpaceOrOrphanRooms()
}
).size
}
val dmRooms = session.roomService().getNotificationCountForRooms(
roomSummaryQueryParams {
memberships = listOf(Membership.JOIN)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) }
}
)
val dmRooms = session.roomService().getNotificationCountForRooms(
roomSummaryQueryParams {
memberships = listOf(Membership.JOIN)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) }
}
)
val otherRooms = session.roomService().getNotificationCountForRooms(
roomSummaryQueryParams {
memberships = listOf(Membership.JOIN)
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
spaceFilter = groupingMethod.toActiveSpaceOrOrphanRooms()
}
)
setState {
copy(
notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites,
notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight || (dmInvites + roomsInvite) > 0,
notificationCountPeople = dmRooms.totalCount + dmInvites,
notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0,
notificationCountRooms = otherRooms.totalCount + roomsInvite,
notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0,
hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0
)
val otherRooms = session.roomService().getNotificationCountForRooms(
roomSummaryQueryParams {
memberships = listOf(Membership.JOIN)
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
spaceFilter = activeSpaceRoomId.toActiveSpaceOrOrphanRooms()
}
}
null -> Unit
)
setState {
copy(
notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites,
notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight || (dmInvites + roomsInvite) > 0,
notificationCountPeople = dmRooms.totalCount + dmInvites,
notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0,
notificationCountRooms = otherRooms.totalCount + roomsInvite,
notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0,
hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0
)
}
}
.launchIn(viewModelScope)
}
private fun RoomGroupingMethod.BySpace.toActiveSpaceOrOrphanRooms(): SpaceFilter {
return spaceSummary?.roomId.toActiveSpaceOrOrphanRooms()
}
}

View file

@ -21,14 +21,13 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.sync.SyncRequestState
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.util.MatrixItem
data class HomeDetailViewState(
val roomGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
val selectedSpace: RoomSummary? = null,
val myMatrixItem: MatrixItem? = null,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val currentTab: HomeTab = HomeTab.RoomList(RoomListDisplayMode.PEOPLE),

View file

@ -32,7 +32,6 @@ class InitSyncStepFormatter @Inject constructor(
InitialSyncStep.ImportingAccount -> R.string.initial_sync_start_importing_account
InitialSyncStep.ImportingAccountCrypto -> R.string.initial_sync_start_importing_account_crypto
InitialSyncStep.ImportingAccountRoom -> R.string.initial_sync_start_importing_account_rooms
InitialSyncStep.ImportingAccountGroups -> R.string.initial_sync_start_importing_account_groups
InitialSyncStep.ImportingAccountData -> R.string.initial_sync_start_importing_account_data
InitialSyncStep.ImportingAccountJoinedRooms -> R.string.initial_sync_start_importing_account_joined_rooms
InitialSyncStep.ImportingAccountInvitedRooms -> R.string.initial_sync_start_importing_account_invited_rooms

View file

@ -23,7 +23,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction
@ -110,8 +109,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
}
combine(
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged(),
appStateHandler.selectedRoomGroupingFlow.flatMapLatest {
appStateHandler.selectedSpaceFlow.distinctUntilChanged(),
appStateHandler.selectedSpaceFlow.flatMapLatest {
roomService.getPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = Membership.activeMemberships()
@ -119,74 +118,57 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
).asFlow()
.throttleFirst(300)
}
) { groupingMethod, _ ->
when (groupingMethod.orNull()) {
is RoomGroupingMethod.ByLegacyGroup -> {
// currently not supported
CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
is RoomGroupingMethod.BySpace -> {
val selectedSpace = appStateHandler.safeActiveSpaceId()
) { selectedSpaceOption, _ ->
val selectedSpace = selectedSpaceOption.orNull()?.roomId
val inviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
roomService.getRoomSummaries(
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
).size
}
val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
roomService.getRoomSummaries(
spaceSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}
).size
}
val totalCount = roomService.getNotificationCountForRooms(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.spaceFilter = SpaceFilter.OrphanRooms.takeIf {
!vectorPreferences.prefSpacesShowAllRoomInHome()
}
}
)
val counts = RoomAggregateNotificationCount(
totalCount.notificationCount + inviteCount,
totalCount.highlightCount + inviteCount
)
val rootCounts = session.spaceService().getRootSpaceSummaries()
.filter {
// filter out current selection
it.roomId != selectedSpace
}
CountInfo(
homeCount = counts,
otherCount = RoomAggregateNotificationCount(
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount,
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount
)
)
}
null -> {
CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
val inviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
roomService.getRoomSummaries(
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
).size
}
val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
roomService.getRoomSummaries(
spaceSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}
).size
}
val totalCount = roomService.getNotificationCountForRooms(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.spaceFilter = SpaceFilter.OrphanRooms.takeIf {
!vectorPreferences.prefSpacesShowAllRoomInHome()
}
}
)
val counts = RoomAggregateNotificationCount(
totalCount.notificationCount + inviteCount,
totalCount.highlightCount + inviteCount
)
val rootCounts = session.spaceService().getRootSpaceSummaries()
.filter {
// filter out current selection
it.roomId != selectedSpace
}
CountInfo(
homeCount = counts,
otherCount = RoomAggregateNotificationCount(
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount,
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount
)
)
}
.flowOn(Dispatchers.Default)
.execute {

View file

@ -32,7 +32,6 @@ import im.vector.app.core.glide.GlideRequests
import im.vector.app.features.autocomplete.command.AutocompleteCommandPresenter
import im.vector.app.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.app.features.autocomplete.emoji.AutocompleteEmojiPresenter
import im.vector.app.features.autocomplete.group.AutocompleteGroupPresenter
import im.vector.app.features.autocomplete.member.AutocompleteMemberItem
import im.vector.app.features.autocomplete.member.AutocompleteMemberPresenter
import im.vector.app.features.autocomplete.room.AutocompleteRoomPresenter
@ -41,7 +40,6 @@ import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem
@ -56,7 +54,6 @@ class AutoCompleter @AssistedInject constructor(
autocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory,
private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory,
private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
) {
@ -89,7 +86,6 @@ class AutoCompleter @AssistedInject constructor(
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, android.R.attr.colorBackground))
setupCommands(backgroundDrawable, editText)
setupMembers(backgroundDrawable, editText)
setupGroups(backgroundDrawable, editText)
setupEmojis(backgroundDrawable, editText)
setupRooms(backgroundDrawable, editText)
}
@ -97,7 +93,6 @@ class AutoCompleter @AssistedInject constructor(
fun clear() {
this.editText = null
autocompleteEmojiPresenter.clear()
autocompleteGroupPresenter.clear()
autocompleteRoomPresenter.clear()
autocompleteCommandPresenter.clear()
autocompleteMemberPresenter.clear()
@ -170,24 +165,6 @@ class AutoCompleter @AssistedInject constructor(
.build()
}
private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText) {
Autocomplete.on<GroupSummary>(editText)
.with(CharPolicy(TRIGGER_AUTO_COMPLETE_GROUPS, true))
.with(autocompleteGroupPresenter)
.with(ELEVATION_DP)
.with(backgroundDrawable)
.with(object : AutocompleteCallback<GroupSummary> {
override fun onPopupItemClicked(editable: Editable, item: GroupSummary): Boolean {
insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_GROUPS, item.toMatrixItem())
return true
}
override fun onPopupVisibilityChanged(shown: Boolean) {
}
})
.build()
}
private fun setupEmojis(backgroundDrawable: Drawable, editText: EditText) {
Autocomplete.on<String>(editText)
.with(CharPolicy(TRIGGER_AUTO_COMPLETE_EMOJIS, false))
@ -262,7 +239,6 @@ class AutoCompleter @AssistedInject constructor(
private const val ELEVATION_DP = 6f
private const val TRIGGER_AUTO_COMPLETE_MEMBERS = '@'
private const val TRIGGER_AUTO_COMPLETE_ROOMS = '#'
private const val TRIGGER_AUTO_COMPLETE_GROUPS = '+'
private const val TRIGGER_AUTO_COMPLETE_EMOJIS = ':'
}
}

View file

@ -61,7 +61,6 @@ import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.space
import im.vector.lib.core.utils.flow.chunk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
@ -219,7 +218,7 @@ class TimelineViewModel @AssistedInject constructor(
if (initialState.switchToParentSpace) {
// We are coming from a notification, try to switch to the most relevant space
// so that when hitting back the room will appear in the list
appStateHandler.getCurrentRoomGroupingMethod()?.space().let { currentSpace ->
appStateHandler.getCurrentSpace().let { currentSpace ->
val currentRoomSummary = room.roomSummary() ?: return@let
// nothing we are good
if ((currentSpace == null && !vectorPreferences.prefSpacesShowAllRoomInHome()) ||

View file

@ -24,7 +24,6 @@ import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
import im.vector.app.space
import javax.inject.Inject
class RoomListFooterController @Inject constructor(
@ -42,7 +41,7 @@ class RoomListFooterController @Inject constructor(
id("filter_footer")
listener(host.listener)
currentFilter(data.roomFilter)
inSpace(data.currentRoomGrouping.invoke()?.space() != null)
inSpace(data.asyncSelectedSpace.invoke() != null)
}
}
else -> {

View file

@ -16,8 +16,443 @@
package im.vector.app.features.home.room.list
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.liveData
import androidx.paging.PagedList
import com.airbnb.mvrx.Async
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.query.SpaceFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import timber.log.Timber
interface RoomListSectionBuilder {
fun buildSections(mode: RoomListDisplayMode): List<RoomsSection>
class RoomListSectionBuilder(
private val session: Session,
private val stringProvider: StringProvider,
private val appStateHandler: AppStateHandler,
private val viewModelScope: CoroutineScope,
private val autoAcceptInvites: AutoAcceptInvites,
private val onUpdatable: (UpdatableLivePageResult) -> Unit,
private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>,
private val onlyOrphansInHome: Boolean = false
) {
private val pagedListConfig = PagedList.Config.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(20)
.setEnablePlaceholders(true)
.setPrefetchDistance(10)
.build()
fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> {
val sections = mutableListOf<RoomsSection>()
val activeSpaceAwareQueries = mutableListOf<RoomListViewModel.ActiveSpaceQueryUpdater>()
when (mode) {
RoomListDisplayMode.PEOPLE -> {
// 4 sections Invites / Fav / Dms / Low Priority
buildDmSections(sections, activeSpaceAwareQueries)
}
RoomListDisplayMode.ROOMS -> {
// 6 sections invites / Fav / Rooms / Low Priority / Server notice / Suggested rooms
buildRoomsSections(sections, activeSpaceAwareQueries)
}
RoomListDisplayMode.FILTERED -> {
// Used when searching for rooms
buildFilteredSection(sections)
}
RoomListDisplayMode.NOTIFICATIONS -> {
buildNotificationsSection(sections, activeSpaceAwareQueries)
}
}
appStateHandler.selectedSpaceFlow
.distinctUntilChanged()
.onEach { selectedSpaceOption ->
val selectedSpace = selectedSpaceOption.orNull()
activeSpaceAwareQueries.onEach { updater ->
updater.updateForSpaceId(selectedSpace?.roomId)
}
}.launchIn(viewModelScope)
return sections
}
private fun buildRoomsSections(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.invitations_header,
notifyOfLocalEcho = true,
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
countRoomAsNotif = true
) {
it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
}
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_favourites,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.bottom_action_rooms,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false)
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.low_priority_header,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null)
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.system_alerts_header,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true)
}
// add suggested rooms
val suggestedRoomsFlow = // MutableLiveData<List<SpaceChildInfo>>()
appStateHandler.selectedSpaceFlow
.distinctUntilChanged()
.flatMapLatest { selectedSpaceOption ->
val selectedSpace = selectedSpaceOption.orNull()
if (selectedSpace == null) {
flowOf(emptyList())
} else {
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val spaceSum = tryOrNull {
session.spaceService()
.querySpaceChildren(selectedSpace.roomId, suggestedOnly = true, null, null)
}
val value = spaceSum?.children.orEmpty().distinctBy { it.childRoomId }
// i need to check if it's already joined.
val filtered = value.filter {
session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true
}
emit(filtered)
}.asFlow()
}
}
val liveSuggestedRooms = MutableLiveData<SuggestedRoomInfo>()
combine(
suggestedRoomsFlow,
suggestedRoomJoiningState.asFlow()
) { rooms, joinStates ->
SuggestedRoomInfo(
rooms,
joinStates
)
}.onEach {
liveSuggestedRooms.postValue(it)
}.launchIn(viewModelScope)
sections.add(
RoomsSection(
sectionName = stringProvider.getString(R.string.suggested_header),
liveSuggested = liveSuggestedRooms,
notifyOfLocalEcho = false,
itemCount = suggestedRoomsFlow.map { suggestions -> suggestions.size }
)
)
}
private fun buildDmSections(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.invitations_header,
notifyOfLocalEcho = true,
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
countRoomAsNotif = true
) {
it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
}
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_favourites,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_people_x,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, null)
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.low_priority_header,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(false, true, null)
}
}
private fun buildNotificationsSection(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.invitations_header,
notifyOfLocalEcho = true,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
},
countRoomAsNotif = true
) {
it.memberships = listOf(Membership.INVITE)
}
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.bottom_action_rooms,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
}
}
private fun buildFilteredSection(sections: MutableList<RoomsSection>) {
// Used when searching for rooms
withQueryParams(
{
it.memberships = Membership.activeMemberships()
},
{ queryParams ->
val name = stringProvider.getString(R.string.bottom_action_rooms)
val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams, getFlattenParents = true)
onUpdatable(updatableFilterLivePageResult)
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
.flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
.distinctUntilChanged()
sections.add(
RoomsSection(
sectionName = name,
livePages = updatableFilterLivePageResult.livePagedList,
itemCount = itemCountFlow
)
)
}
)
}
private fun addSection(
sections: MutableList<RoomsSection>,
activeSpaceUpdaters: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>,
@StringRes nameRes: Int,
notifyOfLocalEcho: Boolean = false,
spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
countRoomAsNotif: Boolean = false,
query: (RoomSummaryQueryParams.Builder) -> Unit
) {
withQueryParams(query) { roomQueryParams ->
val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
val liveQueryParams = MutableStateFlow(updatedQueryParams)
val itemCountFlow = liveQueryParams
.flatMapLatest {
session.roomService().getRoomCountLive(it).asFlow()
}
.flowOn(Dispatchers.Main)
.distinctUntilChanged()
val name = stringProvider.getString(nameRes)
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
pagedListConfig
)
when (spaceFilterStrategy) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) {
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
spaceFilter = roomId?.toActiveSpaceOrOrphanRooms()
)
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
}
})
}
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) {
if (roomId != null) {
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
spaceFilter = SpaceFilter.ActiveSpace(roomId)
)
} else {
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
spaceFilter = null
)
}
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
}
})
}
RoomListViewModel.SpaceFilterStrategy.NONE -> {
// we ignore current space for this one
}
}
val livePagedList = filteredPagedRoomSummariesLive.livePagedList
// use it also as a source to update count
livePagedList.asFlow()
.onEach {
Timber.v("Thread space list: ${Thread.currentThread()}")
sections.find { it.sectionName == name }
?.notificationCount
?.postValue(
if (countRoomAsNotif) {
RoomAggregateNotificationCount(it.size, it.size)
} else {
session.roomService().getNotificationCountForRooms(
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
)
}
)
}
.flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
sections.add(
RoomsSection(
sectionName = name,
livePages = livePagedList,
notifyOfLocalEcho = notifyOfLocalEcho,
itemCount = itemCountFlow
)
)
}
}
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
block(roomSummaryQueryParams { builder.invoke(this) })
}
internal fun RoomSummaryQueryParams.process(spaceFilter: RoomListViewModel.SpaceFilterStrategy, currentSpace: String?): RoomSummaryQueryParams {
return when (spaceFilter) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
copy(
spaceFilter = currentSpace?.toActiveSpaceOrOrphanRooms()
)
}
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
copy(
spaceFilter = currentSpace?.let { SpaceFilter.ActiveSpace(it) }
)
}
RoomListViewModel.SpaceFilterStrategy.NONE -> this
}
}
}

View file

@ -1,290 +0,0 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list
import androidx.annotation.StringRes
import androidx.lifecycle.asFlow
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
class RoomListSectionBuilderGroup(
private val coroutineScope: CoroutineScope,
private val session: Session,
private val stringProvider: StringProvider,
private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites,
private val onUpdatable: (UpdatableLivePageResult) -> Unit
) : RoomListSectionBuilder {
override fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> {
val activeGroupAwareQueries = mutableListOf<UpdatableLivePageResult>()
val sections = mutableListOf<RoomsSection>()
val actualGroupId = appStateHandler.safeActiveGroupId()
when (mode) {
RoomListDisplayMode.PEOPLE -> {
// 4 sections Invites / Fav / Dms / Low Priority
buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId)
}
RoomListDisplayMode.ROOMS -> {
// 5 sections invites / Fav / Rooms / Low Priority / Server notice
buildRoomsSections(sections, activeGroupAwareQueries, actualGroupId)
}
RoomListDisplayMode.FILTERED -> {
// Used when searching for rooms
withQueryParams(
{
it.memberships = Membership.activeMemberships()
},
{ qpm ->
val name = stringProvider.getString(R.string.bottom_action_rooms)
val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm, getFlattenParents = true)
onUpdatable(updatableFilterLivePageResult)
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
.flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
.distinctUntilChanged()
sections.add(
RoomsSection(
sectionName = name,
livePages = updatableFilterLivePageResult.livePagedList,
itemCount = itemCountFlow
)
)
}
)
}
RoomListDisplayMode.NOTIFICATIONS -> {
if (autoAcceptInvites.showInvites()) {
addSection(
sections,
activeGroupAwareQueries,
R.string.invitations_header,
true
) {
it.memberships = listOf(Membership.INVITE)
it.activeGroupId = actualGroupId
}
}
addSection(
sections,
activeGroupAwareQueries,
R.string.bottom_action_rooms,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
it.activeGroupId = actualGroupId
}
}
}
appStateHandler.selectedRoomGroupingFlow
.distinctUntilChanged()
.onEach { groupingMethod ->
val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
activeGroupAwareQueries.onEach { updater ->
updater.queryParams = updater.queryParams.copy(activeGroupId = selectedGroupId)
}
}.launchIn(coroutineScope)
return sections
}
private fun buildRoomsSections(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>,
actualGroupId: String?
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections,
activeSpaceAwareQueries,
R.string.invitations_header,
true
) {
it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.activeGroupId = actualGroupId
}
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_favourites,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
it.activeGroupId = actualGroupId
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_rooms,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false)
it.activeGroupId = actualGroupId
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.low_priority_header,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null)
it.activeGroupId = actualGroupId
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.system_alerts_header,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true)
it.activeGroupId = actualGroupId
}
}
private fun buildPeopleSections(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>,
actualGroupId: String?
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections,
activeSpaceAwareQueries,
R.string.invitations_header,
true
) {
it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.activeGroupId = actualGroupId
}
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_favourites,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
it.activeGroupId = actualGroupId
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_people_x,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, null)
it.activeGroupId = actualGroupId
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.low_priority_header,
false
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(false, true, null)
it.activeGroupId = actualGroupId
}
}
private fun addSection(
sections: MutableList<RoomsSection>,
activeSpaceUpdaters: MutableList<UpdatableLivePageResult>,
@StringRes nameRes: Int,
notifyOfLocalEcho: Boolean = false,
query: (RoomSummaryQueryParams.Builder) -> Unit
) {
withQueryParams(query) { roomQueryParams ->
val name = stringProvider.getString(nameRes)
session.roomService().getFilteredPagedRoomSummariesLive(roomQueryParams)
.also {
activeSpaceUpdaters.add(it)
}.livePagedList
.let { livePagedList ->
// use it also as a source to update count
livePagedList.asFlow()
.onEach {
sections.find { it.sectionName == name }
?.notificationCount
?.postValue(session.roomService().getNotificationCountForRooms(roomQueryParams))
}
.flowOn(Dispatchers.Default)
.launchIn(coroutineScope)
sections.add(
RoomsSection(
sectionName = name,
livePages = livePagedList,
notifyOfLocalEcho = notifyOfLocalEcho,
itemCount = session.roomService().getRoomCountLive(roomQueryParams).asFlow()
)
)
}
}
}
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
block(roomSummaryQueryParams { builder.invoke(this) })
}
}

View file

@ -1,459 +0,0 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.liveData
import androidx.paging.PagedList
import com.airbnb.mvrx.Async
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import im.vector.app.space
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.query.SpaceFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import timber.log.Timber
class RoomListSectionBuilderSpace(
private val session: Session,
private val stringProvider: StringProvider,
private val appStateHandler: AppStateHandler,
private val viewModelScope: CoroutineScope,
private val autoAcceptInvites: AutoAcceptInvites,
private val onUpdatable: (UpdatableLivePageResult) -> Unit,
private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>,
private val onlyOrphansInHome: Boolean = false
) : RoomListSectionBuilder {
private val pagedListConfig = PagedList.Config.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(20)
.setEnablePlaceholders(true)
.setPrefetchDistance(10)
.build()
override fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> {
val sections = mutableListOf<RoomsSection>()
val activeSpaceAwareQueries = mutableListOf<RoomListViewModel.ActiveSpaceQueryUpdater>()
when (mode) {
RoomListDisplayMode.PEOPLE -> {
// 4 sections Invites / Fav / Dms / Low Priority
buildDmSections(sections, activeSpaceAwareQueries)
}
RoomListDisplayMode.ROOMS -> {
// 6 sections invites / Fav / Rooms / Low Priority / Server notice / Suggested rooms
buildRoomsSections(sections, activeSpaceAwareQueries)
}
RoomListDisplayMode.FILTERED -> {
// Used when searching for rooms
buildFilteredSection(sections)
}
RoomListDisplayMode.NOTIFICATIONS -> {
buildNotificationsSection(sections, activeSpaceAwareQueries)
}
}
appStateHandler.selectedRoomGroupingFlow
.distinctUntilChanged()
.onEach { groupingMethod ->
val selectedSpace = groupingMethod.orNull()?.space()
activeSpaceAwareQueries.onEach { updater ->
updater.updateForSpaceId(selectedSpace?.roomId)
}
}.launchIn(viewModelScope)
return sections
}
private fun buildRoomsSections(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.invitations_header,
notifyOfLocalEcho = true,
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
countRoomAsNotif = true
) {
it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
}
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_favourites,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.bottom_action_rooms,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false)
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.low_priority_header,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null)
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.system_alerts_header,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true)
}
// add suggested rooms
val suggestedRoomsFlow = // MutableLiveData<List<SpaceChildInfo>>()
appStateHandler.selectedRoomGroupingFlow
.distinctUntilChanged()
.flatMapLatest { groupingMethod ->
val selectedSpace = groupingMethod.orNull()?.space()
if (selectedSpace == null) {
flowOf(emptyList())
} else {
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val spaceSum = tryOrNull {
session.spaceService()
.querySpaceChildren(selectedSpace.roomId, suggestedOnly = true, null, null)
}
val value = spaceSum?.children.orEmpty().distinctBy { it.childRoomId }
// i need to check if it's already joined.
val filtered = value.filter {
session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true
}
emit(filtered)
}.asFlow()
}
}
val liveSuggestedRooms = MutableLiveData<SuggestedRoomInfo>()
combine(
suggestedRoomsFlow,
suggestedRoomJoiningState.asFlow()
) { rooms, joinStates ->
SuggestedRoomInfo(
rooms,
joinStates
)
}.onEach {
liveSuggestedRooms.postValue(it)
}.launchIn(viewModelScope)
sections.add(
RoomsSection(
sectionName = stringProvider.getString(R.string.suggested_header),
liveSuggested = liveSuggestedRooms,
notifyOfLocalEcho = false,
itemCount = suggestedRoomsFlow.map { suggestions -> suggestions.size }
)
)
}
private fun buildDmSections(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.invitations_header,
notifyOfLocalEcho = true,
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
countRoomAsNotif = true
) {
it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
}
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_favourites,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.bottom_action_people_x,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, null)
}
addSection(
sections,
activeSpaceAwareQueries,
R.string.low_priority_header,
false,
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.roomTagQueryFilter = RoomTagQueryFilter(false, true, null)
}
}
private fun buildNotificationsSection(
sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>
) {
if (autoAcceptInvites.showInvites()) {
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.invitations_header,
notifyOfLocalEcho = true,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
},
countRoomAsNotif = true
) {
it.memberships = listOf(Membership.INVITE)
}
}
addSection(
sections = sections,
activeSpaceUpdaters = activeSpaceAwareQueries,
nameRes = R.string.bottom_action_rooms,
notifyOfLocalEcho = false,
spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
}
}
private fun buildFilteredSection(sections: MutableList<RoomsSection>) {
// Used when searching for rooms
withQueryParams(
{
it.memberships = Membership.activeMemberships()
},
{ queryParams ->
val name = stringProvider.getString(R.string.bottom_action_rooms)
val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams, getFlattenParents = true)
onUpdatable(updatableFilterLivePageResult)
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
.flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
.distinctUntilChanged()
sections.add(
RoomsSection(
sectionName = name,
livePages = updatableFilterLivePageResult.livePagedList,
itemCount = itemCountFlow
)
)
}
)
}
private fun addSection(
sections: MutableList<RoomsSection>,
activeSpaceUpdaters: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>,
@StringRes nameRes: Int,
notifyOfLocalEcho: Boolean = false,
spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
countRoomAsNotif: Boolean = false,
query: (RoomSummaryQueryParams.Builder) -> Unit
) {
withQueryParams(query) { roomQueryParams ->
val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
val liveQueryParams = MutableStateFlow(updatedQueryParams)
val itemCountFlow = liveQueryParams
.flatMapLatest {
session.roomService().getRoomCountLive(it).asFlow()
}
.flowOn(Dispatchers.Main)
.distinctUntilChanged()
val name = stringProvider.getString(nameRes)
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
pagedListConfig
)
when (spaceFilterStrategy) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) {
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
spaceFilter = roomId.toActiveSpaceOrOrphanRooms()
)
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
}
})
}
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) {
if (roomId != null) {
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
spaceFilter = SpaceFilter.ActiveSpace(roomId)
)
} else {
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
spaceFilter = null
)
}
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
}
})
}
RoomListViewModel.SpaceFilterStrategy.NONE -> {
// we ignore current space for this one
}
}
val livePagedList = filteredPagedRoomSummariesLive.livePagedList
// use it also as a source to update count
livePagedList.asFlow()
.onEach {
Timber.v("Thread space list: ${Thread.currentThread()}")
sections.find { it.sectionName == name }
?.notificationCount
?.postValue(
if (countRoomAsNotif) {
RoomAggregateNotificationCount(it.size, it.size)
} else {
session.roomService().getNotificationCountForRooms(
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
)
}
)
}
.flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
sections.add(
RoomsSection(
sectionName = name,
livePages = livePagedList,
notifyOfLocalEcho = notifyOfLocalEcho,
itemCount = itemCountFlow
)
)
}
}
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
block(roomSummaryQueryParams { builder.invoke(this) })
}
internal fun RoomSummaryQueryParams.process(spaceFilter: RoomListViewModel.SpaceFilterStrategy, currentSpace: String?): RoomSummaryQueryParams {
return when (spaceFilter) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
copy(
spaceFilter = currentSpace.toActiveSpaceOrOrphanRooms()
)
}
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
copy(
spaceFilter = currentSpace?.let { SpaceFilter.ActiveSpace(it) }
)
}
RoomListViewModel.SpaceFilterStrategy.NONE -> this
}
}
}

View file

@ -26,7 +26,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
@ -101,11 +100,11 @@ class RoomListViewModel @AssistedInject constructor(
observeMembershipChanges()
observeLocalRooms()
appStateHandler.selectedRoomGroupingFlow
appStateHandler.selectedSpaceFlow
.distinctUntilChanged()
.execute {
copy(
currentRoomGrouping = it.invoke()?.orNull()?.let { Success(it) } ?: Loading()
asyncSelectedSpace = it.invoke()?.orNull()?.let { Success(it) } ?: Loading()
)
}
@ -146,8 +145,7 @@ class RoomListViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
private val roomListSectionBuilder = if (appStateHandler.getCurrentRoomGroupingMethod() is RoomGroupingMethod.BySpace) {
RoomListSectionBuilderSpace(
private val roomListSectionBuilder = RoomListSectionBuilder(
session,
stringProvider,
appStateHandler,
@ -159,17 +157,6 @@ class RoomListViewModel @AssistedInject constructor(
suggestedRoomJoiningState,
!vectorPreferences.prefSpacesShowAllRoomInHome()
)
} else {
RoomListSectionBuilderGroup(
viewModelScope,
session,
stringProvider,
appStateHandler,
autoAcceptInvites
) {
updatableQuery = it
}
}
val sections: List<RoomsSection> by lazy {
roomListSectionBuilder.buildSections(initialState.displayMode)

View file

@ -19,9 +19,9 @@ package im.vector.app.features.home.room.list
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.RoomGroupingMethod
import im.vector.app.features.home.RoomListDisplayMode
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
data class RoomListViewState(
@ -30,7 +30,7 @@ data class RoomListViewState(
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
val currentUserName: String? = null,
val currentRoomGrouping: Async<RoomGroupingMethod> = Uninitialized,
val asyncSelectedSpace: Async<RoomSummary?> = Uninitialized,
val localRoomIds: Set<String> = emptySet()
) : MavericksState {

View file

@ -94,7 +94,6 @@ class PillsPostProcessor @AssistedInject constructor(
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
is PermalinkData.UserLink -> permalinkData.toMatrixItem(roomId)
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
is PermalinkData.GroupLink -> permalinkData.toMatrixItem()
else -> null
} ?: return null
return createPillImageSpan(matrixItem)
@ -118,9 +117,4 @@ class PillsPostProcessor @AssistedInject constructor(
// Exclude event link (used in reply events, we do not want to pill the "in reply to")
null
}
private fun PermalinkData.GroupLink.toMatrixItem(): MatrixItem? {
val group = sessionHolder.getSafeActiveSession()?.groupService()?.getGroupSummary(groupId)
return MatrixItem.GroupItem(groupId, group?.displayName, group?.avatarUrl)
}
}

View file

@ -75,8 +75,7 @@ class MatrixToBottomSheet :
views.matrixToCardContentLoading.isVisible = state.matrixItem is Incomplete
showFragment(MatrixToUserFragment::class, Bundle())
}
is PermalinkData.GroupLink -> Unit
is PermalinkData.FallbackLink -> Unit
is PermalinkData.FallbackLink,
is PermalinkData.RoomEmailInviteLink -> Unit
}
}

View file

@ -76,13 +76,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
copy(matrixItem = Loading())
}
}
is PermalinkData.GroupLink -> {
// Not yet supported
}
is PermalinkData.FallbackLink -> {
// Not yet supported
}
is PermalinkData.RoomEmailInviteLink -> Unit
is PermalinkData.RoomEmailInviteLink,
is PermalinkData.FallbackLink -> Unit
}
viewModelScope.launch(Dispatchers.IO) {
resolveLink(initialState)
@ -186,10 +181,6 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
}
}
}
is PermalinkData.GroupLink -> {
// not yet supported
_viewEvents.post(MatrixToViewEvents.Dismiss)
}
is PermalinkData.RoomEmailInviteLink,
is PermalinkData.FallbackLink -> {
_viewEvents.post(MatrixToViewEvents.Dismiss)

View file

@ -23,6 +23,7 @@ import android.net.Uri
import android.os.Build
import android.view.View
import android.view.Window
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityOptionsCompat
@ -32,11 +33,8 @@ import androidx.core.view.ViewCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
import im.vector.app.features.VectorFeatures
import im.vector.app.features.VectorFeatures.OnboardingVariant
import im.vector.app.features.analytics.AnalyticsTracker
@ -105,11 +103,11 @@ import im.vector.app.features.spaces.people.SpacePeopleActivity
import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder
import im.vector.app.space
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget
@ -169,7 +167,7 @@ class DefaultNavigator @Inject constructor(
analyticsTracker.capture(
sessionHolder.getActiveSession().getRoomSummary(roomId).toAnalyticsViewRoom(
trigger = trigger,
groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()
selectedSpace = appStateHandler.getCurrentSpace()
)
)
}
@ -284,14 +282,6 @@ class DefaultNavigator @Inject constructor(
}
}
override fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean) {
if (context is VectorBaseActivity<*>) {
context.notImplemented("Open group detail")
} else {
context.toast(R.string.not_implemented)
}
}
override fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean) {
val args = RoomMemberProfileArgs(userId = userId, roomId = roomId)
val intent = RoomMemberProfileActivity.newIntent(context, args)
@ -328,25 +318,10 @@ class DefaultNavigator @Inject constructor(
}
override fun openRoomDirectory(context: Context, initialFilter: String) {
when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> {
// TODO should open list of rooms of this group
val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
context.startActivity(intent)
}
is RoomGroupingMethod.BySpace -> {
val selectedSpace = groupingMethod.space()
if (selectedSpace == null) {
val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
context.startActivity(intent)
} else {
SpaceExploreActivity.newIntent(context, selectedSpace.roomId).let {
context.startActivity(it)
}
}
}
null -> Unit
}
when (val currentSpace = appStateHandler.getCurrentSpace()) {
null -> RoomDirectoryActivity.getIntent(context, initialFilter)
else -> SpaceExploreActivity.newIntent(context, currentSpace.roomId)
}.start(context)
}
override fun openCreateRoom(context: Context, initialName: String, openAfterCreate: Boolean) {
@ -355,54 +330,24 @@ class DefaultNavigator @Inject constructor(
}
override fun openCreateDirectRoom(context: Context) {
val intent = when (val currentGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> {
CreateDirectRoomActivity.getIntent(context)
}
is RoomGroupingMethod.BySpace -> {
if (currentGroupingMethod.spaceSummary != null) {
SpacePeopleActivity.newIntent(context, currentGroupingMethod.spaceSummary.roomId)
} else {
CreateDirectRoomActivity.getIntent(context)
}
}
else -> null
} ?: return
context.startActivity(intent)
when (val currentSpace = appStateHandler.getCurrentSpace()) {
null -> CreateDirectRoomActivity.getIntent(context)
else -> SpacePeopleActivity.newIntent(context, currentSpace.roomId)
}.start(context)
}
override fun openInviteUsersToRoom(context: Context, roomId: String) {
when (val currentGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> {
val intent = InviteUsersToRoomActivity.getIntent(context, roomId)
context.startActivity(intent)
}
is RoomGroupingMethod.BySpace -> {
if (currentGroupingMethod.spaceSummary != null) {
// let user decides if he does it from space or room
(context as? AppCompatActivity)?.supportFragmentManager?.let { fm ->
InviteRoomSpaceChooserBottomSheet.newInstance(
currentGroupingMethod.spaceSummary.roomId,
roomId,
object : InviteRoomSpaceChooserBottomSheet.InteractionListener {
override fun inviteToSpace(spaceId: String) {
val intent = InviteUsersToRoomActivity.getIntent(context, spaceId)
context.startActivity(intent)
}
when (val currentSpace = appStateHandler.getCurrentSpace()) {
null -> InviteUsersToRoomActivity.getIntent(context, roomId).start(context)
else -> showInviteToDialog(context, currentSpace, roomId)
}
}
override fun inviteToRoom(roomId: String) {
val intent = InviteUsersToRoomActivity.getIntent(context, roomId)
context.startActivity(intent)
}
}
).show(fm, InviteRoomSpaceChooserBottomSheet::class.java.name)
}
} else {
val intent = InviteUsersToRoomActivity.getIntent(context, roomId)
context.startActivity(intent)
}
private fun showInviteToDialog(context: Context, currentSpace: RoomSummary, roomId: String) {
(context as? AppCompatActivity)?.supportFragmentManager?.let { fragmentManager ->
InviteRoomSpaceChooserBottomSheet.showInstance(fragmentManager, currentSpace.roomId, roomId) { itemId ->
InviteUsersToRoomActivity.getIntent(context, itemId).start(context)
}
null -> Unit
}
}
@ -449,6 +394,10 @@ class DefaultNavigator @Inject constructor(
context.startActivity(KeysBackupManageActivity.intent(context))
}
override fun showGroupsUnsupportedWarning(context: Context) {
Toast.makeText(context, context.getString(R.string.permalink_unsupported_groups), Toast.LENGTH_LONG).show()
}
override fun openRoomProfile(context: Context, roomId: String, directAccess: Int?) {
context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess))
}
@ -658,4 +607,8 @@ class DefaultNavigator @Inject constructor(
) {
activityResultLauncher.launch(screenCaptureIntent)
}
private fun Intent.start(context: Context) {
context.startActivity(this)
}
}

View file

@ -112,7 +112,7 @@ interface Navigator {
fun openKeysBackupManager(context: Context)
fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean = false)
fun showGroupsUnsupportedWarning(context: Context)
fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)

View file

@ -90,55 +90,84 @@ class PermalinkHandler @Inject constructor(
buildTask: Boolean
): Boolean {
return when (permalinkData) {
is PermalinkData.RoomLink -> {
val roomId = permalinkData.getRoomId()
val session = activeSessionHolder.getSafeActiveSession()
is PermalinkData.RoomLink -> handleRoomLink(permalinkData, rawLink, context, navigationInterceptor, buildTask)
is PermalinkData.UserLink -> handleUserLink(permalinkData, rawLink, context, navigationInterceptor, buildTask)
is PermalinkData.FallbackLink -> handleFallbackLink(permalinkData, context)
is PermalinkData.RoomEmailInviteLink -> handleRoomInviteLink(permalinkData, context)
}
}
val rootThreadEventId = permalinkData.eventId?.let { eventId ->
val room = roomId?.let { session?.getRoom(it) }
private suspend fun handleRoomLink(
permalinkData: PermalinkData.RoomLink,
rawLink: Uri,
context: Context,
navigationInterceptor: NavigationInterceptor?,
buildTask: Boolean
): Boolean {
val roomId = permalinkData.getRoomId()
val session = activeSessionHolder.getSafeActiveSession()
val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) {
eventId
} else {
null
}
}
openRoom(
navigationInterceptor,
context = context,
roomId = roomId,
permalinkData = permalinkData,
rawLink = rawLink,
buildTask = buildTask,
rootThreadEventId = rootThreadEventId
)
true
val rootThreadEventId = permalinkData.eventId?.let { eventId ->
val room = roomId?.let { session?.getRoom(it) }
val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) {
eventId
} else {
null
}
is PermalinkData.GroupLink -> {
navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
true
}
is PermalinkData.UserLink -> {
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
}
true
}
is PermalinkData.FallbackLink -> {
false
}
is PermalinkData.RoomEmailInviteLink -> {
val data = RoomPreviewData(
roomId = permalinkData.roomId,
roomName = permalinkData.roomName,
avatarUrl = permalinkData.roomAvatarUrl,
fromEmailInvite = permalinkData,
roomType = permalinkData.roomType
)
navigator.openRoomPreview(context, data)
true
}
openRoom(
navigationInterceptor,
context = context,
roomId = roomId,
permalinkData = permalinkData,
rawLink = rawLink,
buildTask = buildTask,
rootThreadEventId = rootThreadEventId
)
return true
}
private fun handleUserLink(
permalinkData: PermalinkData.UserLink,
rawLink: Uri,
context: Context,
navigationInterceptor: NavigationInterceptor?,
buildTask: Boolean
): Boolean {
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
}
return true
}
private fun handleRoomInviteLink(
permalinkData: PermalinkData.RoomEmailInviteLink,
context: Context
): Boolean {
val data = RoomPreviewData(
roomId = permalinkData.roomId,
roomName = permalinkData.roomName,
avatarUrl = permalinkData.roomAvatarUrl,
fromEmailInvite = permalinkData,
roomType = permalinkData.roomType
)
navigator.openRoomPreview(context, data)
return true
}
private suspend fun handleFallbackLink(
permalinkData: PermalinkData.FallbackLink,
context: Context
): Boolean {
return if (permalinkData.isLegacyGroupLink) {
withContext(Dispatchers.Main) {
navigator.showGroupsUnsupportedWarning(context)
}
true
} else {
false
}
}

View file

@ -22,6 +22,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.airbnb.mvrx.args
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
@ -47,12 +48,7 @@ class InviteRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<Bo
@Inject
lateinit var activeSessionHolder: ActiveSessionHolder
interface InteractionListener {
fun inviteToSpace(spaceId: String)
fun inviteToRoom(roomId: String)
}
var interactionListener: InteractionListener? = null
var onItemSelected: ((roomId: String) -> Unit)? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceInviteChooserBinding {
return BottomSheetSpaceInviteChooserBinding.inflate(inflater, container, false)
@ -72,7 +68,7 @@ class InviteRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<Bo
views.inviteToSpaceButton.subTitle = getString(R.string.invite_to_space_with_name_desc, spaceName)
views.inviteToSpaceButton.debouncedClicks {
dismiss()
interactionListener?.inviteToSpace(inviteArgs.spaceId)
onItemSelected?.invoke(inviteArgs.spaceId)
}
views.inviteToRoomOnly.isVisible = true
@ -80,17 +76,21 @@ class InviteRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<Bo
views.inviteToRoomOnly.subTitle = getString(R.string.invite_just_to_this_room_desc, spaceName)
views.inviteToRoomOnly.debouncedClicks {
dismiss()
interactionListener?.inviteToRoom(inviteArgs.roomId)
onItemSelected?.invoke(inviteArgs.roomId)
}
}
companion object {
fun newInstance(spaceId: String, roomId: String, interactionListener: InteractionListener): InviteRoomSpaceChooserBottomSheet {
return InviteRoomSpaceChooserBottomSheet().apply {
this.interactionListener = interactionListener
fun showInstance(
fragmentManager: FragmentManager,
spaceId: String,
roomId: String,
onItemSelected: (roomId: String) -> Unit
) {
InviteRoomSpaceChooserBottomSheet().apply {
this.onItemSelected = onItemSelected
setArguments(Args(spaceId, roomId))
}
}.show(fragmentManager, InviteRoomSpaceChooserBottomSheet::class.java.name)
}
}
}

View file

@ -17,7 +17,6 @@
package im.vector.app.features.spaces
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class SpaceListAction : VectorViewModelAction {
@ -29,6 +28,4 @@ sealed class SpaceListAction : VectorViewModelAction {
data class MoveSpace(val spaceId: String, val delta: Int) : SpaceListAction()
data class OnStartDragging(val spaceId: String, val expanded: Boolean) : SpaceListAction()
data class OnEndDragging(val spaceId: String, val expanded: Boolean) : SpaceListAction()
data class SelectLegacyGroup(val groupSummary: GroupSummary?) : SpaceListAction()
}

View file

@ -34,7 +34,6 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGroupListBinding
import im.vector.app.features.home.HomeActivitySharedAction
import im.vector.app.features.home.HomeSharedActionViewModel
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
@ -76,19 +75,16 @@ class SpaceListFragment @Inject constructor(
}
override fun onDragReleased(model: SpaceSummaryItem?, itemView: View?) {
// Timber.v("VAL: onModelMoved from $fromPositionM to $toPositionM ${model?.matrixItem?.getBestName()}")
if (toPositionM == null || fromPositionM == null) return
val movingSpace = model?.matrixItem?.id ?: return
viewModel.handle(SpaceListAction.MoveSpace(movingSpace, toPositionM!! - fromPositionM!!))
}
override fun clearView(model: SpaceSummaryItem?, itemView: View?) {
// Timber.v("VAL: clearView ${model?.matrixItem?.getBestName()}")
itemView?.elevation = initialElevation ?: 0f
}
override fun onModelMoved(fromPosition: Int, toPosition: Int, modelBeingMoved: SpaceSummaryItem?, itemView: View?) {
// Timber.v("VAL: onModelMoved incremental from $fromPosition to $toPosition ${modelBeingMoved?.matrixItem?.getBestName()}")
if (fromPositionM == null) {
fromPositionM = fromPosition
}
@ -97,7 +93,6 @@ class SpaceListFragment @Inject constructor(
}
override fun isDragEnabledForModel(model: SpaceSummaryItem?): Boolean {
// Timber.v("VAL: isDragEnabledForModel ${model?.matrixItem?.getBestName()}")
return model?.canDrag == true
}
})
@ -105,10 +100,9 @@ class SpaceListFragment @Inject constructor(
viewModel.observeViewEvents {
when (it) {
is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id))
is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(it.groupingMethodHasChanged))
is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace)
is SpaceListViewEvents.OpenGroup -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(it.groupingMethodHasChanged))
is SpaceListViewEvents.OpenSpaceInvite -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id))
SpaceListViewEvents.CloseDrawer -> sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
}
}
}
@ -149,10 +143,6 @@ class SpaceListFragment @Inject constructor(
viewModel.handle(SpaceListAction.AddSpace)
}
override fun onGroupSelected(groupSummary: GroupSummary?) {
viewModel.handle(SpaceListAction.SelectLegacyGroup(groupSummary))
}
override fun sendFeedBack() {
sharedActionViewModel.post(HomeActivitySharedAction.SendSpaceFeedBack)
}

View file

@ -22,9 +22,8 @@ import im.vector.app.core.platform.VectorViewEvents
* Transient events for group list screen.
*/
sealed class SpaceListViewEvents : VectorViewEvents {
data class OpenSpace(val groupingMethodHasChanged: Boolean) : SpaceListViewEvents()
data class OpenSpaceSummary(val id: String) : SpaceListViewEvents()
data class OpenSpaceInvite(val id: String) : SpaceListViewEvents()
object AddSpace : SpaceListViewEvents()
data class OpenGroup(val groupingMethodHasChanged: Boolean) : SpaceListViewEvents()
object CloseDrawer : SpaceListViewEvents()
}

View file

@ -24,7 +24,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
@ -33,8 +32,6 @@ import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.group
import im.vector.app.space
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@ -50,7 +47,6 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.Membership
@ -79,10 +75,7 @@ class SpaceListViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<SpaceListViewModel, SpaceListViewState> by hiltMavericksViewModelFactory()
// private var currentGroupingMethod : RoomGroupingMethod? = null
init {
session.userService().getUserLive(session.myUserId)
.asFlow()
.setOnEach {
@ -93,20 +86,14 @@ class SpaceListViewModel @AssistedInject constructor(
observeSpaceSummaries()
// observeSelectionState()
appStateHandler.selectedRoomGroupingFlow
appStateHandler.selectedSpaceFlow
.distinctUntilChanged()
.setOnEach {
.setOnEach { selectedSpaceOption ->
copy(
selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
selectedSpace = selectedSpaceOption.orNull()
)
}
session.groupService().getGroupSummariesLive(groupSummaryQueryParams {})
.asFlow()
.setOnEach {
copy(legacyGroups = it)
}
// XXX there should be a way to refactor this and share it
session.roomService().getPagedRoomSummariesLive(
roomSummaryQueryParams {
@ -154,7 +141,6 @@ class SpaceListViewModel @AssistedInject constructor(
SpaceListAction.AddSpace -> handleAddSpace()
is SpaceListAction.ToggleExpand -> handleToggleExpand(action)
is SpaceListAction.OpenSpaceInvite -> handleSelectSpaceInvite(action)
is SpaceListAction.SelectLegacyGroup -> handleSelectGroup(action)
is SpaceListAction.MoveSpace -> handleMoveSpace(action)
is SpaceListAction.OnEndDragging -> handleEndDragging()
is SpaceListAction.OnStartDragging -> handleStartDragging()
@ -229,26 +215,16 @@ class SpaceListViewModel @AssistedInject constructor(
}
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
val groupingMethod = state.selectedGroupingMethod
if (groupingMethod is RoomGroupingMethod.ByLegacyGroup || groupingMethod.space()?.roomId != action.spaceSummary?.roomId) {
if (state.selectedSpace?.roomId != action.spaceSummary?.roomId) {
analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSwitchSpace))
setState { copy(selectedGroupingMethod = RoomGroupingMethod.BySpace(action.spaceSummary)) }
setState { copy(selectedSpace = action.spaceSummary) }
appStateHandler.setCurrentSpace(action.spaceSummary?.roomId)
_viewEvents.post(SpaceListViewEvents.OpenSpace(groupingMethod is RoomGroupingMethod.ByLegacyGroup))
_viewEvents.post(SpaceListViewEvents.CloseDrawer)
} else {
analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSelectedSpace))
}
}
private fun handleSelectGroup(action: SpaceListAction.SelectLegacyGroup) = withState { state ->
val groupingMethod = state.selectedGroupingMethod
if (groupingMethod is RoomGroupingMethod.BySpace || groupingMethod.group()?.groupId != action.groupSummary?.groupId) {
setState { copy(selectedGroupingMethod = RoomGroupingMethod.ByLegacyGroup(action.groupSummary)) }
appStateHandler.setCurrentGroup(action.groupSummary?.groupId)
_viewEvents.post(SpaceListViewEvents.OpenGroup(groupingMethod is RoomGroupingMethod.BySpace))
}
}
private fun handleSelectSpaceInvite(action: SpaceListAction.OpenSpaceInvite) {
_viewEvents.post(SpaceListViewEvents.OpenSpaceInvite(action.spaceSummary.roomId))
}

View file

@ -19,8 +19,6 @@ package im.vector.app.features.spaces
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.RoomGroupingMethod
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.MatrixItem
@ -28,11 +26,10 @@ import org.matrix.android.sdk.api.util.MatrixItem
data class SpaceListViewState(
val myMxItem: Async<MatrixItem.UserItem> = Uninitialized,
val asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
val selectedSpace: RoomSummary? = null,
val rootSpacesOrdered: List<RoomSummary>? = null,
val spaceOrderInfo: Map<String, String?>? = null,
val spaceOrderLocalEchos: Map<String, String?>? = null,
val legacyGroups: List<GroupSummary>? = null,
val expandedStates: Map<String, Boolean> = emptyMap(),
val homeAggregateCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0)
) : MavericksState

View file

@ -18,20 +18,11 @@ package im.vector.app.features.spaces
import com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.genericHeaderItem
import im.vector.app.features.grouplist.groupSummaryItem
import im.vector.app.features.grouplist.homeSpaceSummaryItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.group
import im.vector.app.space
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.group.model.GroupSummary
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
@ -41,7 +32,6 @@ import javax.inject.Inject
class SpaceSummaryController @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider,
private val stringProvider: StringProvider,
) : EpoxyController() {
@ -57,59 +47,18 @@ class SpaceSummaryController @Inject constructor(
override fun buildModels() {
val nonNullViewState = viewState ?: return
val host = this
buildGroupModels(
nonNullViewState.asyncSpaces(),
nonNullViewState.selectedGroupingMethod,
nonNullViewState.selectedSpace,
nonNullViewState.rootSpacesOrdered,
nonNullViewState.expandedStates,
nonNullViewState.homeAggregateCount
)
if (!nonNullViewState.legacyGroups.isNullOrEmpty()) {
genericFooterItem {
id("legacy_space")
text(" ".toEpoxyCharSequence())
}
genericHeaderItem {
id("legacy_groups")
text(host.stringProvider.getString(R.string.groups_header))
textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
}
// add home for communities
nonNullViewState.myMxItem.invoke()?.let { mxItem ->
groupSummaryItem {
avatarRenderer(host.avatarRenderer)
id("all_communities")
matrixItem(mxItem.copy(displayName = host.stringProvider.getString(R.string.group_all_communities)))
selected(
nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup &&
nonNullViewState.selectedGroupingMethod.group() == null
)
listener { host.callback?.onGroupSelected(null) }
}
}
nonNullViewState.legacyGroups.forEach { groupSummary ->
groupSummaryItem {
avatarRenderer(host.avatarRenderer)
id(groupSummary.groupId)
matrixItem(groupSummary.toMatrixItem())
selected(
nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup &&
nonNullViewState.selectedGroupingMethod.group()?.groupId == groupSummary.groupId
)
listener { host.callback?.onGroupSelected(groupSummary) }
}
}
}
}
private fun buildGroupModels(
summaries: List<RoomSummary>?,
selected: RoomGroupingMethod,
selectedSpace: RoomSummary?,
rootSpaces: List<RoomSummary>?,
expandedStates: Map<String, Boolean>,
homeCount: RoomAggregateNotificationCount
@ -137,7 +86,7 @@ class SpaceSummaryController @Inject constructor(
homeSpaceSummaryItem {
id("space_home")
selected(selected is RoomGroupingMethod.BySpace && selected.space() == null)
selected(selectedSpace == null)
countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight))
listener { host.callback?.onSpaceSelected(null) }
}
@ -145,7 +94,7 @@ class SpaceSummaryController @Inject constructor(
rootSpaces
?.filter { it.membership != Membership.INVITE }
?.forEach { roomSummary ->
val isSelected = selected is RoomGroupingMethod.BySpace && roomSummary.roomId == selected.space()?.roomId
val isSelected = roomSummary.roomId == selectedSpace?.roomId
// does it have children?
val subSpaces = roomSummary.spaceChildren?.filter { childInfo ->
summaries?.any { it.roomId == childInfo.childRoomId }.orFalse()
@ -178,7 +127,7 @@ class SpaceSummaryController @Inject constructor(
if (hasChildren && expanded) {
// it's expanded
subSpaces?.forEach { child ->
buildSubSpace(roomSummary.roomId, summaries, expandedStates, selected, child, 1, 3)
buildSubSpace(roomSummary.roomId, summaries, expandedStates, selectedSpace, child, 1, 3)
}
}
}
@ -193,7 +142,7 @@ class SpaceSummaryController @Inject constructor(
idPrefix: String,
summaries: List<RoomSummary>?,
expandedStates: Map<String, Boolean>,
selected: RoomGroupingMethod,
selectedSpace: RoomSummary?,
info: SpaceChildInfo, currentDepth: Int, maxDepth: Int
) {
val host = this
@ -204,7 +153,7 @@ class SpaceSummaryController @Inject constructor(
summaries.any { it.roomId == childInfo.childRoomId }
}?.sortedWith(subSpaceComparator)
val expanded = expandedStates[childSummary.roomId] == true
val isSelected = selected is RoomGroupingMethod.BySpace && childSummary.roomId == selected.space()?.roomId
val isSelected = childSummary.roomId == selectedSpace?.roomId
val id = "$idPrefix:${childSummary.roomId}"
@ -229,7 +178,7 @@ class SpaceSummaryController @Inject constructor(
if (expanded) {
subSpaces?.forEach {
buildSubSpace(id, summaries, expandedStates, selected, it, currentDepth + 1, maxDepth)
buildSubSpace(id, summaries, expandedStates, selectedSpace, it, currentDepth + 1, maxDepth)
}
}
}
@ -240,7 +189,6 @@ class SpaceSummaryController @Inject constructor(
fun onSpaceSettings(spaceSummary: RoomSummary)
fun onToggleExpand(spaceSummary: RoomSummary)
fun onAddSpaceSelected()
fun onGroupSelected(groupSummary: GroupSummary?)
fun sendFeedBack()
}
}

View file

@ -67,30 +67,10 @@ class SharedPreferencesUiStateRepository @Inject constructor(
}
}
override fun storeSelectedGroup(groupId: String?, sessionId: String) {
sharedPreferences.edit {
putString("$KEY_SELECTED_GROUP@$sessionId", groupId)
}
}
override fun storeGroupingMethod(isSpace: Boolean, sessionId: String) {
sharedPreferences.edit {
putBoolean("$KEY_SELECTED_METHOD@$sessionId", isSpace)
}
}
override fun getSelectedGroup(sessionId: String): String? {
return sharedPreferences.getString("$KEY_SELECTED_GROUP@$sessionId", null)
}
override fun getSelectedSpace(sessionId: String): String? {
return sharedPreferences.getString("$KEY_SELECTED_SPACE@$sessionId", null)
}
override fun isGroupingMethodSpace(sessionId: String): Boolean {
return sharedPreferences.getBoolean("$KEY_SELECTED_METHOD@$sessionId", true)
}
override fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>) {
sharedPreferences.edit {
putStringSet("$KEY_CUSTOM_DIRECTORY_HOMESERVER@$sessionId", servers)
@ -110,8 +90,6 @@ class SharedPreferencesUiStateRepository @Inject constructor(
private const val VALUE_DISPLAY_MODE_ROOMS = 2
private const val KEY_SELECTED_SPACE = "UI_STATE_SELECTED_SPACE"
private const val KEY_SELECTED_GROUP = "UI_STATE_SELECTED_GROUP"
private const val KEY_SELECTED_METHOD = "UI_STATE_SELECTED_METHOD"
private const val KEY_CUSTOM_DIRECTORY_HOMESERVER = "KEY_CUSTOM_DIRECTORY_HOMESERVER"
}

View file

@ -34,13 +34,7 @@ interface UiStateRepository {
// TODO Handle SharedPreference per session in a better way, also to cleanup when login out
fun storeSelectedSpace(spaceId: String?, sessionId: String)
fun storeSelectedGroup(groupId: String?, sessionId: String)
fun storeGroupingMethod(isSpace: Boolean, sessionId: String)
fun getSelectedSpace(sessionId: String): String?
fun getSelectedGroup(sessionId: String): String?
fun isGroupingMethodSpace(sessionId: String): Boolean
fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>)
fun getCustomRoomDirectoryHomeservers(sessionId: String): Set<String>

View file

@ -29,7 +29,6 @@ import im.vector.app.features.createdirect.DirectRoomHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getUser
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
@ -42,7 +41,6 @@ class UserCodeSharedViewModel @AssistedInject constructor(
private val session: Session,
private val stringProvider: StringProvider,
private val directRoomHelper: DirectRoomHelper,
private val rawService: RawService
) : VectorViewModel<UserCodeState, UserCodeActions, UserCodeShareViewEvents>(initialState) {
companion object : MavericksViewModelFactory<UserCodeSharedViewModel, UserCodeState> by hiltMavericksViewModelFactory()
@ -129,15 +127,11 @@ class UserCodeSharedViewModel @AssistedInject constructor(
)
}
}
is PermalinkData.GroupLink -> {
// not yet supported
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
}
is PermalinkData.RoomEmailInviteLink,
is PermalinkData.FallbackLink -> {
// not yet supported
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
}
is PermalinkData.RoomEmailInviteLink -> Unit
}
_viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen)
}

View file

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.core.platform.CheckableConstraintLayout 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:id="@+id/itemGroupLayout"
android:layout_width="match_parent"
android:layout_height="65dp"
android:background="@drawable/bg_space_item"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
tools:viewBindingIgnore="true">
<ImageView
android:id="@+id/groupAvatarImageView"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_gravity="center"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:duplicateParentState="true"
android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/groupBottomSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/room_round_avatars" />
<TextView
android:id="@+id/groupNameView"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/groupBottomSeparator"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron"
app:layout_constraintStart_toEndOf="@id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/groupAvatarChevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toTopOf="@id/groupBottomSeparator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?vctr_content_primary"
tools:ignore="MissingPrefix" />
<View
android:id="@+id/groupBottomSeparator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?vctr_list_separator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</im.vector.app.core.platform.CheckableConstraintLayout>

View file

@ -174,7 +174,8 @@
<string name="initial_sync_start_importing_account_joined_rooms">Initial sync:\nLoading your conversations\nIf you\'ve joined lots of rooms, this might take a while</string>
<string name="initial_sync_start_importing_account_invited_rooms">Initial sync:\nImporting invited rooms</string>
<string name="initial_sync_start_importing_account_left_rooms">Initial sync:\nImporting left rooms</string>
<string name="initial_sync_start_importing_account_groups">Initial sync:\nImporting communities</string>
<!--TODO:delete-->
<string name="initial_sync_start_importing_account_groups" tools:ignore="UnusedResources">Initial sync:\nImporting communities</string>
<string name="initial_sync_start_importing_account_data">Initial sync:\nImporting account data</string>
<string name="initial_sync_request_title">Initial sync request</string>
@ -442,7 +443,8 @@
<string name="settings_room_directory_show_all_rooms_summary">Show all rooms in the room directory, including rooms with explicit content.</string>
<!-- Groups fragment -->
<string name="groups_header">Communities</string>
<!--TODO: delete-->
<string name="groups_header" tools:ignore="UnusedResources">Communities</string>
<string name="spaces_header">Spaces</string>
@ -1614,7 +1616,8 @@
<string name="error_no_network">No network. Please check your Internet connection.</string>
<string name="change_room_directory_network">"Change network"</string>
<string name="please_wait">"Please wait…"</string>
<string name="group_all_communities">"All Communities"</string>
<!--TODO: delete-->
<string name="group_all_communities" tools:ignore="UnusedResources">"All Communities"</string>
<string name="room_preview_no_preview">"This room can't be previewed"</string>
<string name="room_preview_not_found">This room is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.</string>
@ -2084,6 +2087,7 @@
<string name="soft_logout_sso_not_same_user_error">The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by ${app_name}.\nPlease first clear data, then sign in again on another account.</string>
<string name="permalink_malformed">Your matrix.to link was malformed</string>
<string name="permalink_unsupported_groups">Cannot open this link: communities have been replaced by spaces</string>
<string name="bug_report_error_too_short">The description is too short</string>
<string name="notification_initial_sync">Initial Sync…</string>