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.crosssigning.PrivateKeysInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo 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.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.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.api.session.room.RoomSortOrder 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>> { fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow<List<RoomSummary>> {
return session.spaceService().getSpaceSummariesLive(queryParams).asFlow() return session.spaceService().getSpaceSummariesLive(queryParams).asFlow()
.startWith(session.coroutineDispatchers.io) { .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.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker 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.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.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
@ -154,11 +153,6 @@ interface Session {
*/ */
fun roomDirectoryService(): RoomDirectoryService fun roomDirectoryService(): RoomDirectoryService
/**
* Returns the GroupService associated with the session.
*/
fun groupService(): GroupService
/** /**
* Returns the UserService associated with the session. * 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 UserLink(val userId: String) : PermalinkData()
data class GroupLink(val groupId: String) : PermalinkData() data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData()
data class FallbackLink(val uri: Uri) : PermalinkData()
} }

View file

@ -61,27 +61,29 @@ object PermalinkParser {
val params = safeFragment val params = safeFragment
.split(MatrixPatterns.SEP_REGEX) .split(MatrixPatterns.SEP_REGEX)
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.map { URLDecoder.decode(it, "UTF-8") }
.take(2) .take(2)
val decodedParams = params
.map { URLDecoder.decode(it, "UTF-8") }
val identifier = params.getOrNull(0) val identifier = params.getOrNull(0)
val extraParameter = params.getOrNull(1) val decodedIdentifier = decodedParams.getOrNull(0)
val extraParameter = decodedParams.getOrNull(1)
return when { return when {
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier)
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) MatrixPatterns.isRoomId(decodedIdentifier) -> {
MatrixPatterns.isRoomId(identifier) -> { handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters)
handleRoomIdCase(fragment, identifier, matrixToUri, extraParameter, viaQueryParameters)
} }
MatrixPatterns.isRoomAlias(identifier) -> { MatrixPatterns.isRoomAlias(decodedIdentifier) -> {
PermalinkData.RoomLink( PermalinkData.RoomLink(
roomIdOrAlias = identifier, roomIdOrAlias = decodedIdentifier,
isRoomAlias = true, isRoomAlias = true,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters 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. * Used to filter room using the current space.
*/ */
val spaceFilter: SpaceFilter?, 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 excludeType: List<String?>? = listOf(RoomType.SPACE)
var includeType: List<String?>? = null var includeType: List<String?>? = null
var spaceFilter: SpaceFilter? = null var spaceFilter: SpaceFilter? = null
var activeGroupId: String? = null
fun build() = RoomSummaryQueryParams( fun build() = RoomSummaryQueryParams(
displayName = displayName, displayName = displayName,
@ -117,7 +112,6 @@ data class RoomSummaryQueryParams(
excludeType = excludeType, excludeType = excludeType,
includeType = includeType, includeType = includeType,
spaceFilter = spaceFilter, spaceFilter = spaceFilter,
activeGroupId = activeGroupId
) )
} }
} }

View file

@ -22,7 +22,6 @@ enum class InitialSyncStep {
ImportingAccount, ImportingAccount,
ImportingAccountCrypto, ImportingAccountCrypto,
ImportingAccountRoom, ImportingAccountRoom,
ImportingAccountGroups,
ImportingAccountData, ImportingAccountData,
ImportingAccountJoinedRooms, ImportingAccountJoinedRooms,
ImportingAccountInvitedRooms, 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") @Json(name = "org.matrix.msc2732.device_unused_fallback_key_types")
val deviceUnusedFallbackKeyTypes: List<String>? = null, 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.BuildConfig
import org.matrix.android.sdk.api.extensions.tryOrNull 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.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
@ -113,19 +112,6 @@ sealed class MatrixItem(
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) 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() { protected fun checkId() {
if (!id.startsWith(getIdPrefix())) { if (!id.startsWith(getIdPrefix())) {
error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}") error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}")
@ -144,7 +130,6 @@ sealed class MatrixItem(
is RoomItem, is RoomItem,
is EveryoneInRoomItem -> '!' is EveryoneInRoomItem -> '!'
is RoomAliasItem -> '#' is RoomAliasItem -> '#'
is GroupItem -> '+'
} }
fun firstLetterOfDisplayName(): String { fun firstLetterOfDisplayName(): String {
@ -196,8 +181,6 @@ sealed class MatrixItem(
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) { fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
MatrixItem.SpaceItem(roomId, displayName, avatarUrl) MatrixItem.SpaceItem(roomId, displayName, avatarUrl)
} else { } 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.MigrateSessionTo029
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 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.MigrateSessionTo031
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -63,7 +64,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000 override fun hashCode() = 1000
val schemaVersion = 31L val schemaVersion = 32L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Realm Session from $oldVersion to $newVersion") 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 < 29) MigrateSessionTo029(realm).perform()
if (oldVersion < 30) MigrateSessionTo030(realm).perform() if (oldVersion < 30) MigrateSessionTo030(realm).perform()
if (oldVersion < 31) MigrateSessionTo031(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") realm.schema.get("RoomSummaryEntity")
?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java) ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, 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 -> ?.transform { obj ->
val creationEvent = realm.where("CurrentStateEventEntity") 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -14,15 +14,15 @@
* limitations under the License. * 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 io.realm.DynamicRealm
import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.util.database.RealmMigrator
@JsonClass(generateAdapter = true) internal class MigrateSessionTo032(realm: DynamicRealm) : RealmMigrator(realm, 32) {
internal data class GroupRooms(
@Json(name = "total_room_count_estimate") val totalRoomCountEstimate: Int? = null, override fun doMigrate(realm: DynamicRealm) {
@Json(name = "chunk") val rooms: List<GroupRoom> = emptyList() 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 if (value != field) field = value
} }
var groupIds: String? = null
set(value) {
if (value != field) field = value
}
@Index @Index
private var membershipStr: String = Membership.NONE.name 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, EventInsertEntity::class,
TimelineEventEntity::class, TimelineEventEntity::class,
FilterEntity::class, FilterEntity::class,
GroupEntity::class,
GroupSummaryEntity::class,
ReadReceiptEntity::class, ReadReceiptEntity::class,
RoomEntity::class, RoomEntity::class,
RoomSummaryEntity::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.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker 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.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.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
@ -97,7 +96,6 @@ internal class DefaultSession @Inject constructor(
private val sessionListeners: SessionListeners, private val sessionListeners: SessionListeners,
private val roomService: Lazy<RoomService>, private val roomService: Lazy<RoomService>,
private val roomDirectoryService: Lazy<RoomDirectoryService>, private val roomDirectoryService: Lazy<RoomDirectoryService>,
private val groupService: Lazy<GroupService>,
private val userService: Lazy<UserService>, private val userService: Lazy<UserService>,
private val filterService: Lazy<FilterService>, private val filterService: Lazy<FilterService>,
private val federationService: Lazy<FederationService>, private val federationService: Lazy<FederationService>,
@ -209,7 +207,6 @@ internal class DefaultSession @Inject constructor(
override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get() override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get()
override fun roomService(): RoomService = roomService.get() override fun roomService(): RoomService = roomService.get()
override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get() override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
override fun groupService(): GroupService = groupService.get()
override fun userService(): UserService = userService.get() override fun userService(): UserService = userService.get()
override fun signOutService(): SignOutService = signOutService.get() override fun signOutService(): SignOutService = signOutService.get()
override fun filterService(): FilterService = filterService.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.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerModule 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.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.homeserver.HomeServerCapabilitiesModule
import org.matrix.android.sdk.internal.session.identity.IdentityModule import org.matrix.android.sdk.internal.session.identity.IdentityModule
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule
@ -74,10 +72,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
SyncModule::class, SyncModule::class,
HomeServerCapabilitiesModule::class, HomeServerCapabilitiesModule::class,
SignOutModule::class, SignOutModule::class,
GroupModule::class,
UserModule::class, UserModule::class,
FilterModule::class, FilterModule::class,
GroupModule::class,
ContentModule::class, ContentModule::class,
CacheModule::class, CacheModule::class,
MediaModule::class, MediaModule::class,
@ -124,8 +120,6 @@ internal interface SessionComponent {
fun inject(worker: RedactEventWorker) fun inject(worker: RedactEventWorker)
fun inject(worker: GetGroupDataWorker)
fun inject(worker: UploadContentWorker) fun inject(worker: UploadContentWorker)
fun inject(worker: SyncWorker) fun inject(worker: SyncWorker)

View file

@ -25,7 +25,7 @@ internal class DisplayNameResolver @Inject constructor(
private val matrixConfiguration: MatrixConfiguration private val matrixConfiguration: MatrixConfiguration
) { ) {
fun getBestName(matrixItem: MatrixItem): String { 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 // Best name is the id, and we keep the displayName of the room for the case we need the first letter
matrixItem.id matrixItem.id
} else { } 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) url.startsWith(MATRIX_TO_URL_BASE) -> url.substring(MATRIX_TO_URL_BASE.length)
clientBaseUrl != null && url.startsWith(clientBaseUrl) -> { clientBaseUrl != null && url.startsWith(clientBaseUrl) -> {
when (PermalinkParser.parse(url)) { 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.RoomLink -> url.substring(clientBaseUrl.length + ROOM_PATH.length)
is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length) is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length)
else -> null else -> null

View file

@ -328,9 +328,6 @@ internal class RoomSummaryDataSource @Inject constructor(
null -> Unit // nop null -> Unit // nop
} }
queryParams.activeGroupId?.let { activeGroupId ->
query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId)
}
return query 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.ContentMapper
import org.matrix.android.sdk.internal.database.mapper.asDomain 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.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.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@ -438,38 +437,6 @@ internal class RoomSummaryUpdater @Inject constructor(
space.notificationCount = notificationCount space.notificationCount = notificationCount
} }
// xxx invites?? // 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 { }.also {
Timber.v("## SPACES: Finish checking room hierarchy in $it ms") Timber.v("## SPACES: Finish checking room hierarchy in $it ms")
} }

View file

@ -16,47 +16,36 @@
package org.matrix.android.sdk.internal.session.sync package org.matrix.android.sdk.internal.session.sync
import androidx.work.ExistingPeriodicWorkPolicy
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.pushrules.PushRuleService 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.pushrules.RuleScope
import org.matrix.android.sdk.api.session.sync.InitialSyncStep 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.RoomsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId 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.SessionListeners
import org.matrix.android.sdk.internal.session.dispatchTo 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.pushrules.ProcessEventForPushTask
import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler 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.PresenceSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler 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.UserAccountDataSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler 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.util.awaitTransaction
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class SyncResponseHandler @Inject constructor( internal class SyncResponseHandler @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
private val sessionListeners: SessionListeners, private val sessionListeners: SessionListeners,
private val workManagerProvider: WorkManagerProvider,
private val roomSyncHandler: RoomSyncHandler, private val roomSyncHandler: RoomSyncHandler,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val groupSyncHandler: GroupSyncHandler,
private val cryptoSyncHandler: CryptoSyncHandler, private val cryptoSyncHandler: CryptoSyncHandler,
private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler, private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler,
private val cryptoService: DefaultCryptoService, 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) // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local)
measureTimeMillis { measureTimeMillis {
Timber.v("Handle rooms") Timber.v("Handle rooms")
reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.7f) { reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.8f) {
if (syncResponse.rooms != null) { if (syncResponse.rooms != null) {
roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter) 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") 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 { measureTimeMillis {
reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) { reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) {
Timber.v("Handle accountData") Timber.v("Handle accountData")
@ -155,9 +133,6 @@ internal class SyncResponseHandler @Inject constructor(
userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
dispatchInvitedRoom(it) dispatchInvitedRoom(it)
} }
syncResponse.groups?.let {
scheduleGroupDataFetchingIfNeeded(it)
}
Timber.v("On sync completed") Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse) 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) { private suspend fun checkPushRules(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean) {
Timber.v("[PushRules] --> checkPushRules") Timber.v("[PushRules] --> checkPushRules")
if (isInitialSync) { 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.crypto.crosssigning.UpdateTrustWorker
import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.session.content.UploadContentWorker 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.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker 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) CheckFactoryWorker(appContext, workerParameters, true)
AddPusherWorker::class.java.name -> AddPusherWorker::class.java.name ->
AddPusherWorker(appContext, workerParameters, sessionManager) AddPusherWorker(appContext, workerParameters, sessionManager)
GetGroupDataWorker::class.java.name ->
GetGroupDataWorker(appContext, workerParameters, sessionManager)
MultipleEventSendingDispatcherWorker::class.java.name -> MultipleEventSendingDispatcherWorker::class.java.name ->
MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager) MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager)
RedactEventWorker::class.java.name -> 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.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary 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.room.model.RoomSummary
import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.SyncRequestState
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton 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. * This class handles the global app state.
* It requires to be added to ProcessLifecycleOwner.get().lifecycle * It requires to be added to ProcessLifecycleOwner.get().lifecycle
@ -68,29 +59,20 @@ class AppStateHandler @Inject constructor(
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) 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?>() private val spaceBackstack = ArrayDeque<String?>()
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? { fun getCurrentSpace(): RoomSummary? {
// XXX we should somehow make it live :/ just a work around return selectedSpaceDataSource.currentValue?.orNull()?.let { spaceSummary ->
// For example just after creating a space and switching to it the activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(spaceSummary.roomId)
// 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 setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false, isForwardNavigation: Boolean = true) { 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 val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
if (currentSpace != null && spaceId == currentSpace.roomId) return if (currentSpace != null && spaceId == currentSpace.roomId) return
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) } val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
@ -100,11 +82,15 @@ class AppStateHandler @Inject constructor(
} }
if (persistNow) { if (persistNow) {
uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, 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) { if (spaceId != null) {
uSession.coroutineScope.launch(Dispatchers.IO) { uSession.coroutineScope.launch(Dispatchers.IO) {
tryOrNull { 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() { private fun observeActiveSession() {
sessionDataSource.stream() sessionDataSource.stream()
.distinctUntilChanged() .distinctUntilChanged()
.onEach { .onEach {
// sessionDataSource could already return a session while activeSession holder still returns null // sessionDataSource could already return a session while activeSession holder still returns null
it.orNull()?.let { session -> it.orNull()?.let { session ->
if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session)
setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session)
} else {
setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session)
}
observeSyncStatus(session) observeSyncStatus(session)
} }
} }
@ -160,11 +127,7 @@ class AppStateHandler @Inject constructor(
fun getSpaceBackstack() = spaceBackstack fun getSpaceBackstack() = spaceBackstack
fun safeActiveSpaceId(): String? { fun safeActiveSpaceId(): String? {
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId return selectedSpaceDataSource.currentValue?.orNull()?.roomId
}
fun safeActiveGroupId(): String? {
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
} }
override fun onResume(owner: LifecycleOwner) { override fun onResume(owner: LifecycleOwner) {
@ -174,15 +137,6 @@ class AppStateHandler @Inject constructor(
override fun onPause(owner: LifecycleOwner) { override fun onPause(owner: LifecycleOwner) {
coroutineScope.coroutineContext.cancelChildren() coroutineScope.coroutineContext.cancelChildren()
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { uiStateRepository.storeSelectedSpace(selectedSpaceDataSource.currentValue?.orNull()?.roomId, session.sessionId)
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)
}
}
} }
} }

View file

@ -16,19 +16,13 @@
package im.vector.app.features.analytics.extensions package im.vector.app.features.analytics.extensions
import im.vector.app.RoomGroupingMethod
import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.analytics.plan.ViewRoom
import org.matrix.android.sdk.api.extensions.orFalse 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.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
fun RoomSummary?.toAnalyticsViewRoom(trigger: ViewRoom.Trigger?, groupingMethod: RoomGroupingMethod? = null, viaKeyboard: Boolean? = null): ViewRoom { fun RoomSummary?.toAnalyticsViewRoom(trigger: ViewRoom.Trigger?, selectedSpace: RoomSummary? = null, viaKeyboard: Boolean? = null): ViewRoom {
val activeSpace = groupingMethod?.let { val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home
when (it) {
is RoomGroupingMethod.BySpace -> it.spaceSummary?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home
else -> null
}
}
return ViewRoom( return ViewRoom(
isDM = this?.isDirect.orFalse(), 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 { fun MatrixItem.getBestName(): String {
// Note: this code is copied from [DisplayNameResolver] in the SDK // 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 // Best name is the id, and we keep the displayName of the room for the case we need the first letter
id id
} else { } 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) { when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(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.OpenSpacePreview -> startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
is HomeActivitySharedAction.AddSpace -> createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this)) is HomeActivitySharedAction.AddSpace -> createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId) is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId) is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK) HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
HomeActivitySharedAction.CloseGroup -> closeGroup() HomeActivitySharedAction.OnCloseSpace -> onCloseSpace()
} }
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
@ -265,17 +264,6 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) 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) { private fun showSpaceSettings(spaceId: String) {
// open bottom sheet // open bottom sheet
SpaceSettingsMenuBottomSheet SpaceSettingsMenuBottomSheet
@ -292,7 +280,7 @@ class HomeActivity :
.show(supportFragmentManager, "SPACE_INVITE") .show(supportFragmentManager, "SPACE_INVITE")
} }
private fun closeGroup() { private fun onCloseSpace() {
views.drawerLayout.openDrawer(GravityCompat.START) views.drawerLayout.openDrawer(GravityCompat.START)
} }

View file

@ -24,8 +24,7 @@ import im.vector.app.core.platform.VectorSharedAction
sealed class HomeActivitySharedAction : VectorSharedAction { sealed class HomeActivitySharedAction : VectorSharedAction {
object OpenDrawer : HomeActivitySharedAction() object OpenDrawer : HomeActivitySharedAction()
object CloseDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction()
data class OpenGroup(val shouldClearFragment: Boolean) : HomeActivitySharedAction() object OnCloseSpace : HomeActivitySharedAction()
object CloseGroup : HomeActivitySharedAction()
object AddSpace : HomeActivitySharedAction() object AddSpace : HomeActivitySharedAction()
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction() data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
data class OpenSpaceInvite(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 com.google.android.material.badge.BadgeDrawable
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.R import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed 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.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel 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.crypto.model.DeviceInfo
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.model.RoomSummary
import javax.inject.Inject import javax.inject.Inject
@ -130,11 +128,8 @@ class HomeDetailFragment @Inject constructor(
views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId() views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId()
} }
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> viewModel.onEach(HomeDetailViewState::selectedSpace) { selectedSpace ->
when (roomGroupingMethod) { onSpaceChange(selectedSpace)
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
}
} }
viewModel.onEach(HomeDetailViewState::currentTab) { currentTab -> viewModel.onEach(HomeDetailViewState::currentTab) { currentTab ->
@ -188,26 +183,16 @@ class HomeDetailFragment @Inject constructor(
} }
private fun navigateBack() { private fun navigateBack() {
try { val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
val lastSpace = appStateHandler.getSpaceBackstack().removeLast() val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
setCurrentSpace(lastSpace) setCurrentSpace(previousSpaceId ?: parentSpaceId)
} catch (e: NoSuchElementException) {
navigateUpOneSpace()
}
} }
private fun setCurrentSpace(spaceId: String?) { private fun setCurrentSpace(spaceId: String?) {
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false) 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() { private fun handleCallStarted() {
dismissLoadingDialog() dismissLoadingDialog()
val fragmentTag = HomeTab.DialPad.toFragmentTag() val fragmentTag = HomeTab.DialPad.toFragmentTag()
@ -227,10 +212,8 @@ class HomeDetailFragment @Inject constructor(
} }
private fun refreshSpaceState() { private fun refreshSpaceState() {
when (val roomGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { appStateHandler.getCurrentSpace()?.let {
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary) onSpaceChange(it)
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
else -> Unit
} }
} }
@ -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?) { private fun onSpaceChange(spaceSummary: RoomSummary?) {
if (spaceSummary == null) { if (spaceSummary == null) {
views.groupToolbarSpaceTitleView.isVisible = false views.groupToolbarSpaceTitleView.isVisible = false
@ -335,16 +309,9 @@ class HomeDetailFragment @Inject constructor(
} }
views.homeToolbarContent.debouncedClicks { views.homeToolbarContent.debouncedClicks {
withState(viewModel) { withState(viewModel) { viewState ->
when (it.roomGroupingMethod) { viewState.selectedSpace?.let {
is RoomGroupingMethod.ByLegacyGroup -> { sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId))
// do nothing
}
is RoomGroupingMethod.BySpace -> {
it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
}
}
} }
} }
} }
@ -499,7 +466,7 @@ class HomeDetailFragment @Inject constructor(
return this return this
} }
override fun onBackPressed(toolbarButton: Boolean) = if (getCurrentSpace() != null) { override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) {
navigateBack() navigateBack()
true true
} else { } else {

View file

@ -23,7 +23,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
@ -208,16 +207,16 @@ class HomeDetailViewModel @AssistedInject constructor(
} }
private fun observeRoomGroupingMethod() { private fun observeRoomGroupingMethod() {
appStateHandler.selectedRoomGroupingFlow appStateHandler.selectedSpaceFlow
.setOnEach { .setOnEach {
copy( copy(
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) selectedSpace = it.orNull()
) )
} }
} }
private fun observeRoomSummaries() { 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 // we use it as a trigger to all changes in room, but do not really load
// the actual models // the actual models
session.roomService().getPagedRoomSummariesLive( session.roomService().getPagedRoomSummariesLive(
@ -229,67 +228,55 @@ class HomeDetailViewModel @AssistedInject constructor(
} }
.throttleFirst(300) .throttleFirst(300)
.onEach { .onEach {
when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { val activeSpaceRoomId = appStateHandler.getCurrentSpace()?.roomId
is RoomGroupingMethod.ByLegacyGroup -> { var dmInvites = 0
// TODO!! var roomsInvite = 0
} if (autoAcceptInvites.showInvites()) {
is RoomGroupingMethod.BySpace -> { dmInvites = session.roomService().getRoomSummaries(
val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId roomSummaryQueryParams {
var dmInvites = 0 memberships = listOf(Membership.INVITE)
var roomsInvite = 0 roomCategoryFilter = RoomCategoryFilter.ONLY_DM
if (autoAcceptInvites.showInvites()) { spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) }
dmInvites = session.roomService().getRoomSummaries( }
roomSummaryQueryParams { ).size
memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) }
}
).size
roomsInvite = session.roomService().getRoomSummaries( roomsInvite = session.roomService().getRoomSummaries(
roomSummaryQueryParams { roomSummaryQueryParams {
memberships = listOf(Membership.INVITE) memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
spaceFilter = groupingMethod.toActiveSpaceOrOrphanRooms() spaceFilter = activeSpaceRoomId.toActiveSpaceOrOrphanRooms()
} }
).size ).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( val otherRooms = session.roomService().getNotificationCountForRooms(
roomSummaryQueryParams { roomSummaryQueryParams {
memberships = listOf(Membership.JOIN) memberships = listOf(Membership.JOIN)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
spaceFilter = activeSpaceRoomId?.let { SpaceFilter.ActiveSpace(it) } spaceFilter = activeSpaceRoomId.toActiveSpaceOrOrphanRooms()
}
)
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
)
} }
} )
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) .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.MavericksState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.app.R 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.room.model.RoomSummary
import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.SyncRequestState
import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
data class HomeDetailViewState( data class HomeDetailViewState(
val roomGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null), val selectedSpace: RoomSummary? = null,
val myMatrixItem: MatrixItem? = null, val myMatrixItem: MatrixItem? = null,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized, val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val currentTab: HomeTab = HomeTab.RoomList(RoomListDisplayMode.PEOPLE), 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.ImportingAccount -> R.string.initial_sync_start_importing_account
InitialSyncStep.ImportingAccountCrypto -> R.string.initial_sync_start_importing_account_crypto InitialSyncStep.ImportingAccountCrypto -> R.string.initial_sync_start_importing_account_crypto
InitialSyncStep.ImportingAccountRoom -> R.string.initial_sync_start_importing_account_rooms 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.ImportingAccountData -> R.string.initial_sync_start_importing_account_data
InitialSyncStep.ImportingAccountJoinedRooms -> R.string.initial_sync_start_importing_account_joined_rooms InitialSyncStep.ImportingAccountJoinedRooms -> R.string.initial_sync_start_importing_account_joined_rooms
InitialSyncStep.ImportingAccountInvitedRooms -> R.string.initial_sync_start_importing_account_invited_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.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
@ -110,8 +109,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
} }
combine( combine(
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged(), appStateHandler.selectedSpaceFlow.distinctUntilChanged(),
appStateHandler.selectedRoomGroupingFlow.flatMapLatest { appStateHandler.selectedSpaceFlow.flatMapLatest {
roomService.getPagedRoomSummariesLive( roomService.getPagedRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = Membership.activeMemberships() this.memberships = Membership.activeMemberships()
@ -119,74 +118,57 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
).asFlow() ).asFlow()
.throttleFirst(300) .throttleFirst(300)
} }
) { groupingMethod, _ -> ) { selectedSpaceOption, _ ->
when (groupingMethod.orNull()) { val selectedSpace = selectedSpaceOption.orNull()?.roomId
is RoomGroupingMethod.ByLegacyGroup -> {
// currently not supported
CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
is RoomGroupingMethod.BySpace -> {
val selectedSpace = appStateHandler.safeActiveSpaceId()
val inviteCount = if (autoAcceptInvites.hideInvites) { val inviteCount = if (autoAcceptInvites.hideInvites) {
0 0
} else { } else {
roomService.getRoomSummaries( roomService.getRoomSummaries(
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
).size ).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 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) .flowOn(Dispatchers.Default)
.execute { .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.AutocompleteCommandPresenter
import im.vector.app.features.autocomplete.command.CommandAutocompletePolicy import im.vector.app.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.app.features.autocomplete.emoji.AutocompleteEmojiPresenter 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.AutocompleteMemberItem
import im.vector.app.features.autocomplete.member.AutocompleteMemberPresenter import im.vector.app.features.autocomplete.member.AutocompleteMemberPresenter
import im.vector.app.features.autocomplete.room.AutocompleteRoomPresenter 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.home.AvatarRenderer
import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.themes.ThemeUtils 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.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem
@ -56,7 +54,6 @@ class AutoCompleter @AssistedInject constructor(
autocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory, autocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory,
private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory, private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory,
private val autocompleteRoomPresenter: AutocompleteRoomPresenter, private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
) { ) {
@ -89,7 +86,6 @@ class AutoCompleter @AssistedInject constructor(
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, android.R.attr.colorBackground)) val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, android.R.attr.colorBackground))
setupCommands(backgroundDrawable, editText) setupCommands(backgroundDrawable, editText)
setupMembers(backgroundDrawable, editText) setupMembers(backgroundDrawable, editText)
setupGroups(backgroundDrawable, editText)
setupEmojis(backgroundDrawable, editText) setupEmojis(backgroundDrawable, editText)
setupRooms(backgroundDrawable, editText) setupRooms(backgroundDrawable, editText)
} }
@ -97,7 +93,6 @@ class AutoCompleter @AssistedInject constructor(
fun clear() { fun clear() {
this.editText = null this.editText = null
autocompleteEmojiPresenter.clear() autocompleteEmojiPresenter.clear()
autocompleteGroupPresenter.clear()
autocompleteRoomPresenter.clear() autocompleteRoomPresenter.clear()
autocompleteCommandPresenter.clear() autocompleteCommandPresenter.clear()
autocompleteMemberPresenter.clear() autocompleteMemberPresenter.clear()
@ -170,24 +165,6 @@ class AutoCompleter @AssistedInject constructor(
.build() .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) { private fun setupEmojis(backgroundDrawable: Drawable, editText: EditText) {
Autocomplete.on<String>(editText) Autocomplete.on<String>(editText)
.with(CharPolicy(TRIGGER_AUTO_COMPLETE_EMOJIS, false)) .with(CharPolicy(TRIGGER_AUTO_COMPLETE_EMOJIS, false))
@ -262,7 +239,6 @@ class AutoCompleter @AssistedInject constructor(
private const val ELEVATION_DP = 6f private const val ELEVATION_DP = 6f
private const val TRIGGER_AUTO_COMPLETE_MEMBERS = '@' private const val TRIGGER_AUTO_COMPLETE_MEMBERS = '@'
private const val TRIGGER_AUTO_COMPLETE_ROOMS = '#' private const val TRIGGER_AUTO_COMPLETE_ROOMS = '#'
private const val TRIGGER_AUTO_COMPLETE_GROUPS = '+'
private const val TRIGGER_AUTO_COMPLETE_EMOJIS = ':' 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.session.coroutineScope
import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.space
import im.vector.lib.core.utils.flow.chunk import im.vector.lib.core.utils.flow.chunk
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -219,7 +218,7 @@ class TimelineViewModel @AssistedInject constructor(
if (initialState.switchToParentSpace) { if (initialState.switchToParentSpace) {
// We are coming from a notification, try to switch to the most relevant space // 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 // 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 val currentRoomSummary = room.roomSummary() ?: return@let
// nothing we are good // nothing we are good
if ((currentSpace == null && !vectorPreferences.prefSpacesShowAllRoomInHome()) || 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.RoomListDisplayMode
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
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 import javax.inject.Inject
class RoomListFooterController @Inject constructor( class RoomListFooterController @Inject constructor(
@ -42,7 +41,7 @@ class RoomListFooterController @Inject constructor(
id("filter_footer") id("filter_footer")
listener(host.listener) listener(host.listener)
currentFilter(data.roomFilter) currentFilter(data.roomFilter)
inSpace(data.currentRoomGrouping.invoke()?.space() != null) inSpace(data.asyncSelectedSpace.invoke() != null)
} }
} }
else -> { else -> {

View file

@ -16,8 +16,443 @@
package im.vector.app.features.home.room.list 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.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 { class RoomListSectionBuilder(
fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> 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.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
@ -101,11 +100,11 @@ class RoomListViewModel @AssistedInject constructor(
observeMembershipChanges() observeMembershipChanges()
observeLocalRooms() observeLocalRooms()
appStateHandler.selectedRoomGroupingFlow appStateHandler.selectedSpaceFlow
.distinctUntilChanged() .distinctUntilChanged()
.execute { .execute {
copy( 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() companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
private val roomListSectionBuilder = if (appStateHandler.getCurrentRoomGroupingMethod() is RoomGroupingMethod.BySpace) { private val roomListSectionBuilder = RoomListSectionBuilder(
RoomListSectionBuilderSpace(
session, session,
stringProvider, stringProvider,
appStateHandler, appStateHandler,
@ -159,17 +157,6 @@ class RoomListViewModel @AssistedInject constructor(
suggestedRoomJoiningState, suggestedRoomJoiningState,
!vectorPreferences.prefSpacesShowAllRoomInHome() !vectorPreferences.prefSpacesShowAllRoomInHome()
) )
} else {
RoomListSectionBuilderGroup(
viewModelScope,
session,
stringProvider,
appStateHandler,
autoAcceptInvites
) {
updatableQuery = it
}
}
val sections: List<RoomsSection> by lazy { val sections: List<RoomsSection> by lazy {
roomListSectionBuilder.buildSections(initialState.displayMode) 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.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.app.RoomGroupingMethod
import im.vector.app.features.home.RoomListDisplayMode 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.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
data class RoomListViewState( data class RoomListViewState(
@ -30,7 +30,7 @@ data class RoomListViewState(
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(), val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized, val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
val currentUserName: String? = null, val currentUserName: String? = null,
val currentRoomGrouping: Async<RoomGroupingMethod> = Uninitialized, val asyncSelectedSpace: Async<RoomSummary?> = Uninitialized,
val localRoomIds: Set<String> = emptySet() val localRoomIds: Set<String> = emptySet()
) : MavericksState { ) : MavericksState {

View file

@ -94,7 +94,6 @@ class PillsPostProcessor @AssistedInject constructor(
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) { val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
is PermalinkData.UserLink -> permalinkData.toMatrixItem(roomId) is PermalinkData.UserLink -> permalinkData.toMatrixItem(roomId)
is PermalinkData.RoomLink -> permalinkData.toMatrixItem() is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
is PermalinkData.GroupLink -> permalinkData.toMatrixItem()
else -> null else -> null
} ?: return null } ?: return null
return createPillImageSpan(matrixItem) 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") // Exclude event link (used in reply events, we do not want to pill the "in reply to")
null 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 views.matrixToCardContentLoading.isVisible = state.matrixItem is Incomplete
showFragment(MatrixToUserFragment::class, Bundle()) showFragment(MatrixToUserFragment::class, Bundle())
} }
is PermalinkData.GroupLink -> Unit is PermalinkData.FallbackLink,
is PermalinkData.FallbackLink -> Unit
is PermalinkData.RoomEmailInviteLink -> Unit is PermalinkData.RoomEmailInviteLink -> Unit
} }
} }

View file

@ -76,13 +76,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
copy(matrixItem = Loading()) copy(matrixItem = Loading())
} }
} }
is PermalinkData.GroupLink -> { is PermalinkData.RoomEmailInviteLink,
// Not yet supported is PermalinkData.FallbackLink -> Unit
}
is PermalinkData.FallbackLink -> {
// Not yet supported
}
is PermalinkData.RoomEmailInviteLink -> Unit
} }
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
resolveLink(initialState) 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.RoomEmailInviteLink,
is PermalinkData.FallbackLink -> { is PermalinkData.FallbackLink -> {
_viewEvents.post(MatrixToViewEvents.Dismiss) _viewEvents.post(MatrixToViewEvents.Dismiss)

View file

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

View file

@ -90,55 +90,84 @@ class PermalinkHandler @Inject constructor(
buildTask: Boolean buildTask: Boolean
): Boolean { ): Boolean {
return when (permalinkData) { return when (permalinkData) {
is PermalinkData.RoomLink -> { is PermalinkData.RoomLink -> handleRoomLink(permalinkData, rawLink, context, navigationInterceptor, buildTask)
val roomId = permalinkData.getRoomId() is PermalinkData.UserLink -> handleUserLink(permalinkData, rawLink, context, navigationInterceptor, buildTask)
val session = activeSessionHolder.getSafeActiveSession() is PermalinkData.FallbackLink -> handleFallbackLink(permalinkData, context)
is PermalinkData.RoomEmailInviteLink -> handleRoomInviteLink(permalinkData, context)
}
}
val rootThreadEventId = permalinkData.eventId?.let { eventId -> private suspend fun handleRoomLink(
val room = roomId?.let { session?.getRoom(it) } 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() val rootThreadEventId = permalinkData.eventId?.let { eventId ->
rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) { val room = roomId?.let { session?.getRoom(it) }
eventId
} else { val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
null rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) {
} eventId
} } else {
openRoom( null
navigationInterceptor,
context = context,
roomId = roomId,
permalinkData = permalinkData,
rawLink = rawLink,
buildTask = buildTask,
rootThreadEventId = rootThreadEventId
)
true
} }
is PermalinkData.GroupLink -> { }
navigator.openGroupDetail(permalinkData.groupId, context, buildTask) openRoom(
true navigationInterceptor,
} context = context,
is PermalinkData.UserLink -> { roomId = roomId,
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) { permalinkData = permalinkData,
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask) rawLink = rawLink,
} buildTask = buildTask,
true rootThreadEventId = rootThreadEventId
} )
is PermalinkData.FallbackLink -> { return true
false }
}
is PermalinkData.RoomEmailInviteLink -> { private fun handleUserLink(
val data = RoomPreviewData( permalinkData: PermalinkData.UserLink,
roomId = permalinkData.roomId, rawLink: Uri,
roomName = permalinkData.roomName, context: Context,
avatarUrl = permalinkData.roomAvatarUrl, navigationInterceptor: NavigationInterceptor?,
fromEmailInvite = permalinkData, buildTask: Boolean
roomType = permalinkData.roomType ): Boolean {
) if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
navigator.openRoomPreview(context, data) navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
true }
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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
@ -47,12 +48,7 @@ class InviteRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<Bo
@Inject @Inject
lateinit var activeSessionHolder: ActiveSessionHolder lateinit var activeSessionHolder: ActiveSessionHolder
interface InteractionListener { var onItemSelected: ((roomId: String) -> Unit)? = null
fun inviteToSpace(spaceId: String)
fun inviteToRoom(roomId: String)
}
var interactionListener: InteractionListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceInviteChooserBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceInviteChooserBinding {
return BottomSheetSpaceInviteChooserBinding.inflate(inflater, container, false) 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.subTitle = getString(R.string.invite_to_space_with_name_desc, spaceName)
views.inviteToSpaceButton.debouncedClicks { views.inviteToSpaceButton.debouncedClicks {
dismiss() dismiss()
interactionListener?.inviteToSpace(inviteArgs.spaceId) onItemSelected?.invoke(inviteArgs.spaceId)
} }
views.inviteToRoomOnly.isVisible = true 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.subTitle = getString(R.string.invite_just_to_this_room_desc, spaceName)
views.inviteToRoomOnly.debouncedClicks { views.inviteToRoomOnly.debouncedClicks {
dismiss() dismiss()
interactionListener?.inviteToRoom(inviteArgs.roomId) onItemSelected?.invoke(inviteArgs.roomId)
} }
} }
companion object { companion object {
fun showInstance(
fun newInstance(spaceId: String, roomId: String, interactionListener: InteractionListener): InviteRoomSpaceChooserBottomSheet { fragmentManager: FragmentManager,
return InviteRoomSpaceChooserBottomSheet().apply { spaceId: String,
this.interactionListener = interactionListener roomId: String,
onItemSelected: (roomId: String) -> Unit
) {
InviteRoomSpaceChooserBottomSheet().apply {
this.onItemSelected = onItemSelected
setArguments(Args(spaceId, roomId)) setArguments(Args(spaceId, roomId))
} }.show(fragmentManager, InviteRoomSpaceChooserBottomSheet::class.java.name)
} }
} }
} }

View file

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

View file

@ -22,9 +22,8 @@ import im.vector.app.core.platform.VectorViewEvents
* Transient events for group list screen. * Transient events for group list screen.
*/ */
sealed class SpaceListViewEvents : VectorViewEvents { sealed class SpaceListViewEvents : VectorViewEvents {
data class OpenSpace(val groupingMethodHasChanged: Boolean) : SpaceListViewEvents()
data class OpenSpaceSummary(val id: String) : SpaceListViewEvents() data class OpenSpaceSummary(val id: String) : SpaceListViewEvents()
data class OpenSpaceInvite(val id: String) : SpaceListViewEvents() data class OpenSpaceInvite(val id: String) : SpaceListViewEvents()
object AddSpace : 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.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel 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.invite.AutoAcceptInvites
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.group
import im.vector.app.space
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged 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.toContent
import org.matrix.android.sdk.api.session.events.model.toModel 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.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.RoomSortOrder
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.Membership 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() companion object : MavericksViewModelFactory<SpaceListViewModel, SpaceListViewState> by hiltMavericksViewModelFactory()
// private var currentGroupingMethod : RoomGroupingMethod? = null
init { init {
session.userService().getUserLive(session.myUserId) session.userService().getUserLive(session.myUserId)
.asFlow() .asFlow()
.setOnEach { .setOnEach {
@ -93,20 +86,14 @@ class SpaceListViewModel @AssistedInject constructor(
observeSpaceSummaries() observeSpaceSummaries()
// observeSelectionState() // observeSelectionState()
appStateHandler.selectedRoomGroupingFlow appStateHandler.selectedSpaceFlow
.distinctUntilChanged() .distinctUntilChanged()
.setOnEach { .setOnEach { selectedSpaceOption ->
copy( 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 // XXX there should be a way to refactor this and share it
session.roomService().getPagedRoomSummariesLive( session.roomService().getPagedRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
@ -154,7 +141,6 @@ class SpaceListViewModel @AssistedInject constructor(
SpaceListAction.AddSpace -> handleAddSpace() SpaceListAction.AddSpace -> handleAddSpace()
is SpaceListAction.ToggleExpand -> handleToggleExpand(action) is SpaceListAction.ToggleExpand -> handleToggleExpand(action)
is SpaceListAction.OpenSpaceInvite -> handleSelectSpaceInvite(action) is SpaceListAction.OpenSpaceInvite -> handleSelectSpaceInvite(action)
is SpaceListAction.SelectLegacyGroup -> handleSelectGroup(action)
is SpaceListAction.MoveSpace -> handleMoveSpace(action) is SpaceListAction.MoveSpace -> handleMoveSpace(action)
is SpaceListAction.OnEndDragging -> handleEndDragging() is SpaceListAction.OnEndDragging -> handleEndDragging()
is SpaceListAction.OnStartDragging -> handleStartDragging() is SpaceListAction.OnStartDragging -> handleStartDragging()
@ -229,26 +215,16 @@ class SpaceListViewModel @AssistedInject constructor(
} }
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
val groupingMethod = state.selectedGroupingMethod if (state.selectedSpace?.roomId != action.spaceSummary?.roomId) {
if (groupingMethod is RoomGroupingMethod.ByLegacyGroup || groupingMethod.space()?.roomId != action.spaceSummary?.roomId) {
analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSwitchSpace)) 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) appStateHandler.setCurrentSpace(action.spaceSummary?.roomId)
_viewEvents.post(SpaceListViewEvents.OpenSpace(groupingMethod is RoomGroupingMethod.ByLegacyGroup)) _viewEvents.post(SpaceListViewEvents.CloseDrawer)
} else { } else {
analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSelectedSpace)) 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) { private fun handleSelectSpaceInvite(action: SpaceListAction.OpenSpaceInvite) {
_viewEvents.post(SpaceListViewEvents.OpenSpaceInvite(action.spaceSummary.roomId)) _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.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized 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.model.RoomSummary
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@ -28,11 +26,10 @@ import org.matrix.android.sdk.api.util.MatrixItem
data class SpaceListViewState( data class SpaceListViewState(
val myMxItem: Async<MatrixItem.UserItem> = Uninitialized, val myMxItem: Async<MatrixItem.UserItem> = Uninitialized,
val asyncSpaces: Async<List<RoomSummary>> = Uninitialized, val asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null), val selectedSpace: RoomSummary? = null,
val rootSpacesOrdered: List<RoomSummary>? = null, val rootSpacesOrdered: List<RoomSummary>? = null,
val spaceOrderInfo: Map<String, String?>? = null, val spaceOrderInfo: Map<String, String?>? = null,
val spaceOrderLocalEchos: Map<String, String?>? = null, val spaceOrderLocalEchos: Map<String, String?>? = null,
val legacyGroups: List<GroupSummary>? = null,
val expandedStates: Map<String, Boolean> = emptyMap(), val expandedStates: Map<String, Boolean> = emptyMap(),
val homeAggregateCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0) val homeAggregateCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0)
) : MavericksState ) : MavericksState

View file

@ -18,20 +18,11 @@ package im.vector.app.features.spaces
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import im.vector.app.R 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.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.grouplist.homeSpaceSummaryItem
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.UnreadCounterBadgeView 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.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.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
@ -41,7 +32,6 @@ import javax.inject.Inject
class SpaceSummaryController @Inject constructor( class SpaceSummaryController @Inject constructor(
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
) : EpoxyController() { ) : EpoxyController() {
@ -57,59 +47,18 @@ class SpaceSummaryController @Inject constructor(
override fun buildModels() { override fun buildModels() {
val nonNullViewState = viewState ?: return val nonNullViewState = viewState ?: return
val host = this
buildGroupModels( buildGroupModels(
nonNullViewState.asyncSpaces(), nonNullViewState.asyncSpaces(),
nonNullViewState.selectedGroupingMethod, nonNullViewState.selectedSpace,
nonNullViewState.rootSpacesOrdered, nonNullViewState.rootSpacesOrdered,
nonNullViewState.expandedStates, nonNullViewState.expandedStates,
nonNullViewState.homeAggregateCount 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( private fun buildGroupModels(
summaries: List<RoomSummary>?, summaries: List<RoomSummary>?,
selected: RoomGroupingMethod, selectedSpace: RoomSummary?,
rootSpaces: List<RoomSummary>?, rootSpaces: List<RoomSummary>?,
expandedStates: Map<String, Boolean>, expandedStates: Map<String, Boolean>,
homeCount: RoomAggregateNotificationCount homeCount: RoomAggregateNotificationCount
@ -137,7 +86,7 @@ class SpaceSummaryController @Inject constructor(
homeSpaceSummaryItem { homeSpaceSummaryItem {
id("space_home") id("space_home")
selected(selected is RoomGroupingMethod.BySpace && selected.space() == null) selected(selectedSpace == null)
countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight)) countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight))
listener { host.callback?.onSpaceSelected(null) } listener { host.callback?.onSpaceSelected(null) }
} }
@ -145,7 +94,7 @@ class SpaceSummaryController @Inject constructor(
rootSpaces rootSpaces
?.filter { it.membership != Membership.INVITE } ?.filter { it.membership != Membership.INVITE }
?.forEach { roomSummary -> ?.forEach { roomSummary ->
val isSelected = selected is RoomGroupingMethod.BySpace && roomSummary.roomId == selected.space()?.roomId val isSelected = roomSummary.roomId == selectedSpace?.roomId
// does it have children? // does it have children?
val subSpaces = roomSummary.spaceChildren?.filter { childInfo -> val subSpaces = roomSummary.spaceChildren?.filter { childInfo ->
summaries?.any { it.roomId == childInfo.childRoomId }.orFalse() summaries?.any { it.roomId == childInfo.childRoomId }.orFalse()
@ -178,7 +127,7 @@ class SpaceSummaryController @Inject constructor(
if (hasChildren && expanded) { if (hasChildren && expanded) {
// it's expanded // it's expanded
subSpaces?.forEach { child -> 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, idPrefix: String,
summaries: List<RoomSummary>?, summaries: List<RoomSummary>?,
expandedStates: Map<String, Boolean>, expandedStates: Map<String, Boolean>,
selected: RoomGroupingMethod, selectedSpace: RoomSummary?,
info: SpaceChildInfo, currentDepth: Int, maxDepth: Int info: SpaceChildInfo, currentDepth: Int, maxDepth: Int
) { ) {
val host = this val host = this
@ -204,7 +153,7 @@ class SpaceSummaryController @Inject constructor(
summaries.any { it.roomId == childInfo.childRoomId } summaries.any { it.roomId == childInfo.childRoomId }
}?.sortedWith(subSpaceComparator) }?.sortedWith(subSpaceComparator)
val expanded = expandedStates[childSummary.roomId] == true 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}" val id = "$idPrefix:${childSummary.roomId}"
@ -229,7 +178,7 @@ class SpaceSummaryController @Inject constructor(
if (expanded) { if (expanded) {
subSpaces?.forEach { 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 onSpaceSettings(spaceSummary: RoomSummary)
fun onToggleExpand(spaceSummary: RoomSummary) fun onToggleExpand(spaceSummary: RoomSummary)
fun onAddSpaceSelected() fun onAddSpaceSelected()
fun onGroupSelected(groupSummary: GroupSummary?)
fun sendFeedBack() 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? { override fun getSelectedSpace(sessionId: String): String? {
return sharedPreferences.getString("$KEY_SELECTED_SPACE@$sessionId", null) 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>) { override fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>) {
sharedPreferences.edit { sharedPreferences.edit {
putStringSet("$KEY_CUSTOM_DIRECTORY_HOMESERVER@$sessionId", servers) 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 VALUE_DISPLAY_MODE_ROOMS = 2
private const val KEY_SELECTED_SPACE = "UI_STATE_SELECTED_SPACE" 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" 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 // TODO Handle SharedPreference per session in a better way, also to cleanup when login out
fun storeSelectedSpace(spaceId: String?, sessionId: String) fun storeSelectedSpace(spaceId: String?, sessionId: String)
fun storeSelectedGroup(groupId: String?, sessionId: String)
fun storeGroupingMethod(isSpace: Boolean, sessionId: String)
fun getSelectedSpace(sessionId: String): String? fun getSelectedSpace(sessionId: String): String?
fun getSelectedGroup(sessionId: String): String?
fun isGroupingMethodSpace(sessionId: String): Boolean
fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>) fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set<String>)
fun getCustomRoomDirectoryHomeservers(sessionId: String): 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull 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.Session
import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.getUser
import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkData
@ -42,7 +41,6 @@ class UserCodeSharedViewModel @AssistedInject constructor(
private val session: Session, private val session: Session,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val rawService: RawService
) : VectorViewModel<UserCodeState, UserCodeActions, UserCodeShareViewEvents>(initialState) { ) : VectorViewModel<UserCodeState, UserCodeActions, UserCodeShareViewEvents>(initialState) {
companion object : MavericksViewModelFactory<UserCodeSharedViewModel, UserCodeState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<UserCodeSharedViewModel, UserCodeState> by hiltMavericksViewModelFactory()
@ -129,15 +127,11 @@ class UserCodeSharedViewModel @AssistedInject constructor(
) )
} }
} }
is PermalinkData.GroupLink -> { is PermalinkData.RoomEmailInviteLink,
// not yet supported
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
}
is PermalinkData.FallbackLink -> { is PermalinkData.FallbackLink -> {
// not yet supported // not yet supported
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
} }
is PermalinkData.RoomEmailInviteLink -> Unit
} }
_viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) _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_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_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_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_start_importing_account_data">Initial sync:\nImporting account data</string>
<string name="initial_sync_request_title">Initial sync request</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> <string name="settings_room_directory_show_all_rooms_summary">Show all rooms in the room directory, including rooms with explicit content.</string>
<!-- Groups fragment --> <!-- 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> <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="error_no_network">No network. Please check your Internet connection.</string>
<string name="change_room_directory_network">"Change network"</string> <string name="change_room_directory_network">"Change network"</string>
<string name="please_wait">"Please wait…"</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_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> <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="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_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="bug_report_error_too_short">The description is too short</string>
<string name="notification_initial_sync">Initial Sync…</string> <string name="notification_initial_sync">Initial Sync…</string>