mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 04:08:44 +03:00
Space first commit
This commit is contained in:
parent
8b5d86f68d
commit
c5fa0a413f
60 changed files with 1523 additions and 59 deletions
|
@ -39,6 +39,8 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
|||
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.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
|
@ -66,6 +68,13 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable<List<SpaceSummary>> {
|
||||
return session.spaceService().getSpaceSummariesLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.spaceService().getSpaceSummaries(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
|
|||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
import org.matrix.android.sdk.api.session.sync.FilterService
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
|
@ -227,6 +228,11 @@ interface Session :
|
|||
*/
|
||||
fun thirdPartyService(): ThirdPartyService
|
||||
|
||||
/**
|
||||
* Returns the space service associated with the session
|
||||
*/
|
||||
fun spaceService(): SpaceService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
|
|
@ -51,6 +51,7 @@ object EventType {
|
|||
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
|
||||
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||
const val STATE_SPACE_CHILD = "m.space.child"
|
||||
|
||||
/**
|
||||
* Note that this Event has been deprecated, see
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue
|
|||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
|
||||
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
||||
return RoomSummaryQueryParams.Builder().apply(init).build()
|
||||
|
@ -36,6 +37,7 @@ data class RoomSummaryQueryParams(
|
|||
val memberships: List<Membership>,
|
||||
val roomCategoryFilter: RoomCategoryFilter?,
|
||||
val roomTagQueryFilter: RoomTagQueryFilter?
|
||||
val excludeType: List<String?>
|
||||
) {
|
||||
|
||||
class Builder {
|
||||
|
@ -46,6 +48,7 @@ data class RoomSummaryQueryParams(
|
|||
var memberships: List<Membership> = Membership.all()
|
||||
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
|
||||
var roomTagQueryFilter: RoomTagQueryFilter? = null
|
||||
var excludeType: List<String?> = listOf(RoomType.SPACE)
|
||||
|
||||
fun build() = RoomSummaryQueryParams(
|
||||
roomId = roomId,
|
||||
|
@ -54,6 +57,7 @@ data class RoomSummaryQueryParams(
|
|||
memberships = memberships,
|
||||
roomCategoryFilter = roomCategoryFilter,
|
||||
roomTagQueryFilter = roomTagQueryFilter
|
||||
excludeType = excludeType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.room.model
|
||||
|
||||
interface IRoomSummary {
|
||||
val roomId: String
|
||||
val displayName: String
|
||||
val name: String
|
||||
val topic: String
|
||||
val avatarUrl: String
|
||||
val canonicalAlias: String?
|
||||
val aliases: List<String>
|
||||
val joinedMembersCount: Int?
|
||||
val invitedMembersCount: Int?
|
||||
val otherMemberIds: List<String>
|
||||
val roomType: String?
|
||||
}
|
|
@ -27,19 +27,19 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|||
* It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
|
||||
*/
|
||||
data class RoomSummary constructor(
|
||||
val roomId: String,
|
||||
override val roomId: String,
|
||||
// Computed display name
|
||||
val displayName: String = "",
|
||||
val name: String = "",
|
||||
val topic: String = "",
|
||||
val avatarUrl: String = "",
|
||||
val canonicalAlias: String? = null,
|
||||
val aliases: List<String> = emptyList(),
|
||||
val isDirect: Boolean = false,
|
||||
val joinedMembersCount: Int? = 0,
|
||||
val invitedMembersCount: Int? = 0,
|
||||
override val displayName: String = "",
|
||||
override val name: String = "",
|
||||
override val topic: String = "",
|
||||
override val avatarUrl: String = "",
|
||||
override val canonicalAlias: String? = null,
|
||||
override val aliases: List<String> = emptyList(),
|
||||
override val joinedMembersCount: Int? = 0,
|
||||
override val invitedMembersCount: Int? = 0,
|
||||
val latestPreviewableEvent: TimelineEvent? = null,
|
||||
val otherMemberIds: List<String> = emptyList(),
|
||||
override val otherMemberIds: List<String> = emptyList(),
|
||||
val isDirect: Boolean = false,
|
||||
val notificationCount: Int = 0,
|
||||
val highlightCount: Int = 0,
|
||||
val hasUnreadMessages: Boolean = false,
|
||||
|
@ -54,8 +54,9 @@ data class RoomSummary constructor(
|
|||
val inviterId: String? = null,
|
||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
|
||||
val hasFailedSending: Boolean = false
|
||||
) {
|
||||
val hasFailedSending: Boolean = false,
|
||||
override val roomType: String? = null
|
||||
) : IRoomSummary {
|
||||
|
||||
val isVersioned: Boolean
|
||||
get() = versioningState != VersioningState.NONE
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.room.model
|
||||
|
||||
object RoomType {
|
||||
|
||||
const val SPACE = "m.space"
|
||||
const val MESSAGING = "m.messaging"
|
||||
}
|
|
@ -21,10 +21,11 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
|
|||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
|
||||
// TODO Give a way to include other initial states
|
||||
class CreateRoomParams {
|
||||
open class CreateRoomParams {
|
||||
/**
|
||||
* A public visibility indicates that the room will be shown in the published room list.
|
||||
* A private visibility will hide the room from the published room list.
|
||||
|
@ -111,6 +112,17 @@ class CreateRoomParams {
|
|||
}
|
||||
}
|
||||
|
||||
var roomType: String? = RoomType.MESSAGING
|
||||
set(value) {
|
||||
field = value
|
||||
if (value != null) {
|
||||
creationContent[CREATION_CONTENT_KEY_ROOM_TYPE] = value
|
||||
} else {
|
||||
// This is the default value, we remove the field
|
||||
creationContent.remove(CREATION_CONTENT_KEY_ROOM_TYPE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The power level content to override in the default power level event
|
||||
*/
|
||||
|
@ -138,5 +150,6 @@ class CreateRoomParams {
|
|||
|
||||
companion object {
|
||||
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
|
||||
private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,5 +26,7 @@ import com.squareup.moshi.JsonClass
|
|||
data class RoomCreateContent(
|
||||
@Json(name = "creator") val creator: String? = null,
|
||||
@Json(name = "room_version") val roomVersion: String? = null,
|
||||
@Json(name = "predecessor") val predecessor: Predecessor? = null
|
||||
@Json(name = "predecessor") val predecessor: Predecessor? = null,
|
||||
// Defines the room type, see #RoomType (user extensible)
|
||||
@Json(name = "type") val type: String? = null
|
||||
)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
|
||||
class CreateSpaceParams : CreateRoomParams() {
|
||||
|
||||
init {
|
||||
roomType = RoomType.SPACE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
|
||||
interface Space {
|
||||
|
||||
fun asRoom() : Room
|
||||
|
||||
suspend fun addRoom(roomId: String)
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
|
||||
typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
|
||||
|
||||
interface SpaceService {
|
||||
|
||||
/**
|
||||
* Create a room asynchronously
|
||||
*/
|
||||
suspend fun createSpace(params: CreateSpaceParams): String
|
||||
|
||||
/**
|
||||
* Get a space from a roomId
|
||||
* @param roomId the roomId to look for.
|
||||
* @return a room with roomId or null if room type is not space
|
||||
*/
|
||||
fun getSpace(spaceId: String): Space?
|
||||
|
||||
/**
|
||||
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
||||
* @return the [LiveData] of List[SpaceSummary]
|
||||
*/
|
||||
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<SpaceSummary>>
|
||||
|
||||
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<SpaceSummary>
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.IRoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
data class SpaceSummary(
|
||||
val spaceId: String,
|
||||
val roomSummary: RoomSummary,
|
||||
val children: List<IRoomSummary>
|
||||
) : IRoomSummary by roomSummary
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.space.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* "content": {
|
||||
* "via": ["example.com"],
|
||||
* "present": true,
|
||||
* "order": "abcd",
|
||||
* "default": true
|
||||
* }
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SpaceChildContent(
|
||||
/**
|
||||
* Key which gives a list of candidate servers that can be used to join the room
|
||||
*/
|
||||
@Json(name = "via") val via: List<String>? = null,
|
||||
/**
|
||||
* present: true key is included to distinguish from a deleted state event
|
||||
*/
|
||||
@Json(name = "present") val present: Boolean? = false,
|
||||
/**
|
||||
* The order key is a string which is used to provide a default ordering of siblings in the room list.
|
||||
* (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last.
|
||||
* orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
|
||||
* or consist of more than 50 characters, are forbidden and should be ignored if received.)
|
||||
*/
|
||||
@Json(name = "order") val order: String? = null,
|
||||
/**
|
||||
* The default flag on a child listing allows a space admin to list the "default" sub-spaces and rooms in that space.
|
||||
* This means that when a user joins the parent space, they will automatically be joined to those default children.
|
||||
*/
|
||||
@Json(name = "default") val default: Boolean? = true
|
||||
)
|
|
@ -22,6 +22,7 @@ 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.roomdirectory.PublicRoom
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import java.util.Locale
|
||||
|
||||
|
@ -86,9 +87,9 @@ sealed class MatrixItem(
|
|||
}
|
||||
|
||||
protected fun checkId() {
|
||||
if (!id.startsWith(getIdPrefix())) {
|
||||
error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}")
|
||||
}
|
||||
// if (!id.startsWith(getIdPrefix())) {
|
||||
// error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}")
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,6 +152,8 @@ fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatar
|
|||
|
||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||
|
||||
fun SpaceSummary.toMatrixItem() = MatrixItem.RoomItem(spaceId, displayName, avatarUrl)
|
||||
|
||||
// If no name is available, use room alias as Riot-Web does
|
||||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
|
||||
|
||||
|
|
|
@ -29,15 +29,17 @@ import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityField
|
|||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
|
||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 9L
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 10L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
|
@ -52,6 +54,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
if (oldVersion <= 6) migrateTo7(realm)
|
||||
if (oldVersion <= 7) migrateTo8(realm)
|
||||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
if (oldVersion <= 9) migrateTo10(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
|
@ -194,4 +197,19 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun migrateTo10(realm: DynamicRealm) {
|
||||
Timber.d("Step 9 -> 10")
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
|
||||
?.transform { obj ->
|
||||
// Should I put messaging type here?
|
||||
obj.setString(RoomSummaryEntityFields.ROOM_TYPE, null)
|
||||
}
|
||||
|
||||
realm.schema.create("SpaceSummaryEntity")
|
||||
?.addField(SpaceSummaryEntityFields.SPACE_ID, String::class.java, FieldAttribute.PRIMARY_KEY)
|
||||
?.addRealmObjectField(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||
inviterId = roomSummaryEntity.inviterId,
|
||||
hasFailedSending = roomSummaryEntity.hasFailedSending
|
||||
hasFailedSending = roomSummaryEntity.hasFailedSending,
|
||||
roomType = roomSummaryEntity.roomType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.space.SpaceSummary
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SpaceSummaryMapper @Inject constructor(private val roomSummaryMapper: RoomSummaryMapper) {
|
||||
|
||||
fun map(spaceSummaryEntity: SpaceSummaryEntity): SpaceSummary {
|
||||
return SpaceSummary(
|
||||
spaceId = spaceSummaryEntity.spaceId,
|
||||
roomSummary = roomSummaryMapper.map(spaceSummaryEntity.roomSummaryEntity!!),
|
||||
children = spaceSummaryEntity.children.map {
|
||||
roomSummaryMapper.map(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -61,6 +61,7 @@ import io.realm.annotations.RealmModule
|
|||
CurrentStateEventEntity::class,
|
||||
UserAccountDataEntity::class,
|
||||
ScalarTokenEntity::class,
|
||||
WellknownIntegrationManagerConfigEntity::class
|
||||
WellknownIntegrationManagerConfigEntity::class,
|
||||
SpaceSummaryEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
internal open class SpaceSummaryEntity(@PrimaryKey var spaceId: String = "",
|
||||
var roomSummaryEntity: RoomSummaryEntity? = null,
|
||||
var children: RealmList<RoomSummaryEntity> = RealmList()
|
||||
// TODO public / private .. and more
|
||||
) : RealmObject() {
|
||||
|
||||
// Do we want to denormalize that ?
|
||||
|
||||
// private var membershipStr: String = Membership.NONE.name
|
||||
// var membership: Membership
|
||||
// get() {
|
||||
// return Membership.valueOf(membershipStr)
|
||||
// }
|
||||
// set(value) {
|
||||
// membershipStr = value.name
|
||||
// }
|
||||
|
||||
companion object
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields
|
||||
|
||||
internal fun SpaceSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<SpaceSummaryEntity> {
|
||||
val query = realm.where<SpaceSummaryEntity>()
|
||||
query.isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`)
|
||||
if (roomId != null) {
|
||||
query.equalTo(SpaceSummaryEntityFields.SPACE_ID, roomId)
|
||||
}
|
||||
query.sort(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME)
|
||||
return query
|
||||
}
|
||||
|
||||
internal fun SpaceSummaryEntity.Companion.findByAlias(realm: Realm, roomAlias: String): SpaceSummaryEntity? {
|
||||
val spaceSummary = realm.where<SpaceSummaryEntity>()
|
||||
.isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`)
|
||||
.equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, roomAlias)
|
||||
.findFirst()
|
||||
if (spaceSummary != null) {
|
||||
return spaceSummary
|
||||
}
|
||||
return realm.where<SpaceSummaryEntity>()
|
||||
.isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`)
|
||||
.contains(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.FLAT_ALIASES, "|$roomAlias")
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun SpaceSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): SpaceSummaryEntity {
|
||||
return where(realm, roomId).findFirst() ?: realm.createObject<SpaceSummaryEntity>(roomId).also {
|
||||
it.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
|
|||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
import org.matrix.android.sdk.api.session.sync.FilterService
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
|
||||
|
@ -121,7 +122,8 @@ internal class DefaultSession @Inject constructor(
|
|||
private val thirdPartyService: Lazy<ThirdPartyService>,
|
||||
private val callSignalingService: Lazy<CallSignalingService>,
|
||||
@UnauthenticatedWithCertificate
|
||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>,
|
||||
private val spaceService: Lazy<SpaceService>
|
||||
) : Session,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
|
@ -265,6 +267,8 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
|
||||
|
||||
override fun spaceService(): SpaceService = spaceService.get()
|
||||
|
||||
override fun getOkHttpClient(): OkHttpClient {
|
||||
return unauthenticatedWithCertificateOkHttpClient.get()
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.commonmark.renderer.html.HtmlRenderer
|
|||
import org.matrix.android.sdk.api.session.file.FileService
|
||||
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
||||
import org.matrix.android.sdk.api.session.room.RoomService
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
|
||||
|
@ -89,6 +90,7 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
|
|||
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
@ -135,6 +137,9 @@ internal abstract class RoomModule {
|
|||
@Binds
|
||||
abstract fun bindRoomService(service: DefaultRoomService): RoomService
|
||||
|
||||
@Binds
|
||||
abstract fun bindSpaceService(service: DefaultSpaceService): SpaceService
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.room.relationship
|
||||
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.whereType
|
||||
|
||||
/**
|
||||
* Relationship between rooms and spaces
|
||||
* The intention is that rooms and spaces form a hierarchy, which clients can use to structure the user's room list into a tree view.
|
||||
* The parent/child relationship can be expressed in one of two ways:
|
||||
* - The admins of a space can advertise rooms and subspaces for their space by setting m.space.child state events.
|
||||
* The state_key is the ID of a child room or space, and the content should contain a via key which gives
|
||||
* a list of candidate servers that can be used to join the room. present: true key is included to distinguish from a deleted state event.
|
||||
*
|
||||
* - Separately, rooms can claim parents via the m.room.parent state event:
|
||||
*/
|
||||
internal class RoomRelationshipHelper(private val realm: Realm,
|
||||
private val roomId: String
|
||||
) {
|
||||
|
||||
fun getDirectChildrenDescriptions(): List<String> {
|
||||
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
|
||||
.findAll()
|
||||
.filter { ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.present == true }
|
||||
.mapNotNull {
|
||||
// ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()
|
||||
it.roomId
|
||||
}
|
||||
}
|
||||
}
|
|
@ -189,6 +189,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
|
||||
}
|
||||
}
|
||||
|
||||
queryParams.excludeType.forEach {
|
||||
query.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, it)
|
||||
}
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
|
|||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
|
@ -36,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntity
|
|||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
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.SpaceSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
|
@ -46,6 +49,7 @@ import org.matrix.android.sdk.internal.di.UserId
|
|||
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import org.matrix.android.sdk.internal.session.room.relationship.RoomRelationshipHelper
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
import timber.log.Timber
|
||||
|
@ -89,6 +93,10 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
|
||||
val roomCreateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CREATE, stateKey = "")?.root
|
||||
|
||||
val roomType = ContentMapper.map(roomCreateEvent?.content).toModel<RoomCreateContent>()?.type
|
||||
roomSummaryEntity.roomType = roomType
|
||||
|
||||
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
|
||||
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
|
@ -152,6 +160,16 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
crossSigningService.onUsersDeviceUpdate(otherRoomMembers)
|
||||
}
|
||||
}
|
||||
|
||||
if (roomType == RoomType.SPACE) {
|
||||
val spaceSummaryEntity = SpaceSummaryEntity.getOrCreate(realm, roomId)
|
||||
spaceSummaryEntity.roomSummaryEntity = roomSummaryEntity
|
||||
spaceSummaryEntity.children.clear()
|
||||
spaceSummaryEntity.children.addAll(
|
||||
RoomRelationshipHelper(realm, roomId).getDirectChildrenDescriptions()
|
||||
.map { RoomSummaryEntity.getOrCreate(realm, roomId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomSummaryEntity.updateHasFailedSending() {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.space.Space
|
||||
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||
|
||||
class DefaultSpace(private val room: Room) : Space {
|
||||
|
||||
override fun asRoom(): Room {
|
||||
return room
|
||||
}
|
||||
|
||||
override suspend fun addRoom(roomId: String) {
|
||||
asRoom().sendStateEvent(
|
||||
eventType = EventType.STATE_SPACE_CHILD,
|
||||
stateKey = roomId,
|
||||
body = SpaceChildContent(present = true).toContent()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
||||
import org.matrix.android.sdk.api.session.space.Space
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
||||
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
|
||||
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
||||
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultSpaceService @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val createRoomTask: CreateRoomTask,
|
||||
private val joinRoomTask: JoinRoomTask,
|
||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||
private val deleteRoomAliasTask: DeleteRoomAliasTask,
|
||||
private val roomGetter: RoomGetter,
|
||||
private val spaceSummaryDataSource: SpaceSummaryDataSource,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : SpaceService {
|
||||
|
||||
override suspend fun createSpace(params: CreateSpaceParams): String {
|
||||
return createRoomTask.execute(params)
|
||||
}
|
||||
|
||||
override fun getSpace(spaceId: String): Space? {
|
||||
return roomGetter.getRoom(spaceId)
|
||||
?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE }
|
||||
?.let { DefaultSpace(it) }
|
||||
}
|
||||
|
||||
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<SpaceSummary>> {
|
||||
return spaceSummaryDataSource.getRoomSummariesLive(queryParams)
|
||||
}
|
||||
|
||||
override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<SpaceSummary> {
|
||||
return spaceSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.space
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.mapper.SpaceSummaryMapper
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.findByAlias
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SpaceSummaryDataSource @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val spaceSummaryMapper: SpaceSummaryMapper
|
||||
) {
|
||||
|
||||
fun getSpaceSummary(roomIdOrAlias: String): SpaceSummary? {
|
||||
return monarchy
|
||||
.fetchCopyMap({
|
||||
if (roomIdOrAlias.startsWith("!")) {
|
||||
// It's a roomId
|
||||
SpaceSummaryEntity.where(it, roomId = roomIdOrAlias).findFirst()
|
||||
} else {
|
||||
// Assume it's a room alias
|
||||
SpaceSummaryEntity.findByAlias(it, roomIdOrAlias)
|
||||
}
|
||||
}, { entity, _ ->
|
||||
spaceSummaryMapper.map(entity)
|
||||
})
|
||||
}
|
||||
|
||||
fun getSpaceSummaryLive(roomId: String): LiveData<Optional<SpaceSummary>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm -> SpaceSummaryEntity.where(realm, roomId).isNotEmpty(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME) },
|
||||
{ spaceSummaryMapper.map(it) }
|
||||
)
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSpaceSummaries(queryParams: SpaceSummaryQueryParams): List<SpaceSummary> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ spaceSummariesQuery(it, queryParams) },
|
||||
{ spaceSummaryMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<SpaceSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ spaceSummariesQuery(it, queryParams) },
|
||||
{ spaceSummaryMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun spaceSummariesQuery(realm: Realm, queryParams: SpaceSummaryQueryParams): RealmQuery<SpaceSummaryEntity> {
|
||||
val query = SpaceSummaryEntity.where(realm)
|
||||
query.process(SpaceSummaryEntityFields.SPACE_ID, queryParams.roomId)
|
||||
query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME, queryParams.displayName)
|
||||
query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||
query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.MEMBERSHIP_STR, queryParams.memberships)
|
||||
query.notEqualTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||
return query
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ import im.vector.app.features.devtools.RoomDevToolStateEventListFragment
|
|||
import im.vector.app.features.discovery.DiscoverySettingsFragment
|
||||
import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
||||
import im.vector.app.features.grouplist.GroupListFragment
|
||||
import im.vector.app.features.grouplist.SpaceListFragment
|
||||
import im.vector.app.features.home.HomeDetailFragment
|
||||
import im.vector.app.features.home.HomeDrawerFragment
|
||||
import im.vector.app.features.home.LoadingFragment
|
||||
|
@ -145,6 +146,11 @@ interface FragmentModule {
|
|||
@FragmentKey(GroupListFragment::class)
|
||||
fun bindGroupListFragment(fragment: GroupListFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(SpaceListFragment::class)
|
||||
fun bindSpaceListFragment(fragment: SpaceListFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomDetailFragment::class)
|
||||
|
|
|
@ -35,6 +35,7 @@ import im.vector.app.features.configuration.VectorConfiguration
|
|||
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.app.features.grouplist.SelectedSpaceDataSource
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
|
@ -115,6 +116,8 @@ interface VectorComponent {
|
|||
|
||||
fun selectedGroupStore(): SelectedGroupDataSource
|
||||
|
||||
fun selectedSpaceStore(): SelectedSpaceDataSource
|
||||
|
||||
fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
|
||||
|
||||
fun activeSessionObservableStore(): ActiveSessionDataSource
|
||||
|
|
|
@ -46,7 +46,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||
PLAIN("/plain", "<message>", R.string.command_description_plain),
|
||||
DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session),
|
||||
CONFETTI("/confetti", "<message>", R.string.command_confetti),
|
||||
SNOW("/snow", "<message>", R.string.command_snow);
|
||||
SNOW("/snow", "<message>", R.string.command_snow),
|
||||
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space);
|
||||
|
||||
val length
|
||||
get() = command.length + 1
|
||||
|
|
|
@ -300,6 +300,18 @@ object CommandParser {
|
|||
val message = textMessage.substring(Command.SNOW.command.length).trim()
|
||||
ParsedCommand.SendChatEffect(ChatEffect.SNOW, message)
|
||||
}
|
||||
Command.CREATE_SPACE.command -> {
|
||||
val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim()
|
||||
val split = rawCommand.split(" ").map { it.trim() }
|
||||
if (split.isEmpty()) {
|
||||
ParsedCommand.ErrorSyntax(Command.CREATE_SPACE)
|
||||
} else {
|
||||
ParsedCommand.CreateSpace(
|
||||
split[0],
|
||||
split.subList(1, split.size)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Unknown command
|
||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||
|
|
|
@ -57,4 +57,5 @@ sealed class ParsedCommand {
|
|||
class SendPoll(val question: String, val options: List<String>) : ParsedCommand()
|
||||
object DiscardSession : ParsedCommand()
|
||||
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
||||
class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand()
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.app.core.platform.VectorViewModel
|
|||
import im.vector.app.core.resources.StringProvider
|
||||
import io.reactivex.Observable
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
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
|
||||
|
@ -96,8 +97,10 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
|||
private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state ->
|
||||
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
|
||||
// We take care of refreshing group data when selecting to be sure we get all the rooms and users
|
||||
viewModelScope.launch {
|
||||
session.getGroup(action.groupSummary.groupId)?.fetchGroupData()
|
||||
tryOrNull {
|
||||
viewModelScope.launch {
|
||||
session.getGroup(action.groupSummary.groupId)?.fetchGroupData()
|
||||
}
|
||||
}
|
||||
setState { copy(selectedGroup = action.groupSummary) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.platform.CheckableConstraintLayout
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_space)
|
||||
abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute var selected: Boolean = false
|
||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||
holder.groupNameView.text = holder.view.context.getString(R.string.group_details_home)
|
||||
holder.rootView.isChecked = selected
|
||||
holder.rootView.context.resources
|
||||
holder.avatarImageView.background = ContextCompat.getDrawable(holder.view.context, R.drawable.space_home_background)
|
||||
holder.avatarImageView.setImageResource(R.drawable.ic_space_home)
|
||||
holder.avatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun dpToPx(resources: Resources, dp: Int): Int {
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp.toFloat(),
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 arrow.core.Option
|
||||
import im.vector.app.core.utils.BehaviorDataSource
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SelectedSpaceDataSource @Inject constructor() : BehaviorDataSource<Option<SpaceSummary>>(Option.empty())
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentGroupListBinding
|
||||
import im.vector.app.features.home.HomeActivitySharedAction
|
||||
import im.vector.app.features.home.HomeSharedActionViewModel
|
||||
import im.vector.app.features.spaces.SpaceListAction
|
||||
import im.vector.app.features.spaces.SpaceListViewEvents
|
||||
import im.vector.app.features.spaces.SpacesListViewModel
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpaceListFragment @Inject constructor(
|
||||
val spaceListViewModelFactory: SpacesListViewModel.Factory,
|
||||
private val spaceController: SpaceSummaryController
|
||||
) : VectorBaseFragment<FragmentGroupListBinding>(), SpaceSummaryController.Callback {
|
||||
|
||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||
private val viewModel: SpacesListViewModel by fragmentViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGroupListBinding {
|
||||
return FragmentGroupListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||
spaceController.callback = this
|
||||
views.stateView.contentView = views.groupListView
|
||||
views.groupListView.configureWith(spaceController)
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
spaceController.callback = null
|
||||
views.groupListView.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
when (state.asyncSpaces) {
|
||||
is Incomplete -> views.stateView.state = StateView.State.Loading
|
||||
is Success -> views.stateView.state = StateView.State.Content
|
||||
}
|
||||
spaceController.update(state)
|
||||
}
|
||||
|
||||
override fun onSpaceSelected(spaceSummary: SpaceSummary) {
|
||||
viewModel.handle(SpaceListAction.SelectSpace(spaceSummary))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.spaces.SpaceListViewState
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpaceSummaryController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider) : EpoxyController() {
|
||||
|
||||
var callback: Callback? = null
|
||||
private var viewState: SpaceListViewState? = null
|
||||
|
||||
init {
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun update(viewState: SpaceListViewState) {
|
||||
this.viewState = viewState
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val nonNullViewState = viewState ?: return
|
||||
buildGroupModels(nonNullViewState.asyncSpaces(), nonNullViewState.selectedSpace)
|
||||
}
|
||||
|
||||
private fun buildGroupModels(summaries: List<SpaceSummary>?, selected: SpaceSummary?) {
|
||||
if (summaries.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
summaries.forEach { groupSummary ->
|
||||
val isSelected = groupSummary.spaceId == selected?.spaceId
|
||||
if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) {
|
||||
homeSpaceSummaryItem {
|
||||
id(groupSummary.spaceId)
|
||||
selected(isSelected)
|
||||
listener { callback?.onSpaceSelected(groupSummary) }
|
||||
}
|
||||
} else {
|
||||
spaceSummaryItem {
|
||||
avatarRenderer(avatarRenderer)
|
||||
id(groupSummary.spaceId)
|
||||
matrixItem(groupSummary.toMatrixItem())
|
||||
selected(isSelected)
|
||||
listener { callback?.onSpaceSelected(groupSummary) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onSpaceSelected(spaceSummary: SpaceSummary)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.platform.CheckableConstraintLayout
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_space)
|
||||
abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var selected: Boolean = false
|
||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||
holder.groupNameView.text = matrixItem.displayName
|
||||
holder.rootView.isChecked = selected
|
||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
avatarRenderer.clear(holder.avatarImageView)
|
||||
super.unbind(holder)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.core.glide.GlideRequest
|
||||
import im.vector.app.core.glide.GlideRequests
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||
import jp.wasabeef.glide.transformations.ColorFilterTransformation
|
||||
|
@ -48,7 +49,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
|
||||
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider) {
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||
private val dimensionConverter: DimensionConverter) {
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 250
|
||||
|
@ -61,6 +63,23 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
DrawableImageViewTarget(imageView))
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
||||
val placeholder = getSpacePlaceholderDrawable(matrixItem)
|
||||
val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
|
||||
glideRequests
|
||||
.load(resolvedUrl)
|
||||
.transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
|
||||
.placeholder(placeholder)
|
||||
.into(DrawableImageViewTarget(imageView))
|
||||
}
|
||||
|
||||
fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
|
||||
renderSpace(
|
||||
matrixItem,
|
||||
imageView, GlideApp.with(imageView))
|
||||
}
|
||||
|
||||
fun clear(imageView: ImageView) {
|
||||
// It can be called after recycler view is destroyed, just silently catch
|
||||
tryOrNull { GlideApp.with(imageView).clear(imageView) }
|
||||
|
@ -159,6 +178,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun getSpacePlaceholderDrawable(matrixItem: MatrixItem): Drawable {
|
||||
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
|
||||
return TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8))
|
||||
}
|
||||
|
||||
// PRIVATE API *********************************************************************************
|
||||
|
||||
private fun buildGlideRequest(glideRequests: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
||||
|
|
|
@ -22,7 +22,10 @@ import android.view.Menu
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -47,11 +50,13 @@ import im.vector.app.features.popup.PopupAlertManager
|
|||
import im.vector.app.features.popup.VerificationVectorAlert
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
|
||||
import im.vector.app.features.spaces.ALL_COMMUNITIES_GROUP_ID
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.BannerState
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import timber.log.Timber
|
||||
|
@ -130,6 +135,11 @@ class HomeDetailFragment @Inject constructor(
|
|||
viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary ->
|
||||
onGroupChange(groupSummary.orNull())
|
||||
}
|
||||
|
||||
viewModel.selectSubscribe(this, HomeDetailViewState::spaceSummary) { spaceSummary ->
|
||||
onSpaceChange(spaceSummary.orNull())
|
||||
}
|
||||
|
||||
viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode ->
|
||||
switchDisplayMode(displayMode)
|
||||
}
|
||||
|
@ -243,6 +253,24 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onSpaceChange(spaceSummary: SpaceSummary?) {
|
||||
spaceSummary?.let {
|
||||
// Use GlideApp with activity context to avoid the glideRequests to be paused
|
||||
if (spaceSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) {
|
||||
// Special case
|
||||
views.groupToolbarAvatarImageView.background = ContextCompat.getDrawable(requireContext(), R.drawable.space_home_background)
|
||||
views.groupToolbarAvatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
views.groupToolbarAvatarImageView.setImageResource(R.drawable.ic_space_home)
|
||||
views.groupToolbarSpaceTitleView.isVisible = false
|
||||
} else {
|
||||
views.groupToolbarAvatarImageView.background = null
|
||||
avatarRenderer.renderSpace(it.toMatrixItem(), views.groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
|
||||
views.groupToolbarSpaceTitleView.isVisible = true
|
||||
views.groupToolbarSpaceTitleView.text = spaceSummary.displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupKeysBackupBanner() {
|
||||
serverBackupStatusViewModel
|
||||
.subscribe(this) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.app.core.platform.EmptyViewEvents
|
|||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.app.features.grouplist.SelectedSpaceDataSource
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -75,6 +76,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
init {
|
||||
observeSyncState()
|
||||
observeSelectedGroupStore()
|
||||
observeSelectedSpaceStore()
|
||||
observeRoomSummaries()
|
||||
}
|
||||
|
||||
|
@ -137,6 +139,17 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeSelectedSpaceStore() {
|
||||
selectedSpaceStore
|
||||
.observe()
|
||||
.subscribe {
|
||||
setState {
|
||||
copy(spaceSummary = it)
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeRoomSummaries() {
|
||||
session.getPagedRoomSummariesLive(
|
||||
roomSummaryQueryParams {
|
||||
|
|
|
@ -22,10 +22,12 @@ import com.airbnb.mvrx.MvRxState
|
|||
import com.airbnb.mvrx.Uninitialized
|
||||
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.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
|
||||
data class HomeDetailViewState(
|
||||
val groupSummary: Option<GroupSummary> = Option.empty(),
|
||||
val spaceSummary: Option<SpaceSummary> = Option.empty(),
|
||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||
val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE,
|
||||
val notificationCountCatchup: Int = 0,
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
|||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
import im.vector.app.databinding.FragmentHomeDrawerBinding
|
||||
import im.vector.app.features.grouplist.GroupListFragment
|
||||
import im.vector.app.features.grouplist.SpaceListFragment
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.usercode.UserCodeActivity
|
||||
|
@ -58,7 +59,11 @@ class HomeDrawerFragment @Inject constructor(
|
|||
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java)
|
||||
if (vectorPreferences.labSpaces()) {
|
||||
replaceChildFragment(R.id.homeDrawerGroupListContainer, SpaceListFragment::class.java)
|
||||
} else {
|
||||
replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java)
|
||||
}
|
||||
}
|
||||
session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser ->
|
||||
val user = optionalUser?.getOrNull()
|
||||
|
|
|
@ -97,6 +97,8 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
|
||||
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
|
@ -821,6 +823,22 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
is ParsedCommand.CreateSpace -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val params = CreateSpaceParams().apply {
|
||||
name = slashCommandResult.name
|
||||
invitedUserIds.addAll(slashCommandResult.invitees)
|
||||
}
|
||||
val spaceId = session.spaceService().createSpace(params)
|
||||
session.spaceService().getSpace(spaceId)?.addRoom(state.roomId)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
}
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
|
|
|
@ -89,19 +89,19 @@ class CreateRoomController @Inject constructor(
|
|||
enabled(enableFormElement)
|
||||
title(stringProvider.getString(R.string.create_room_public_title))
|
||||
summary(stringProvider.getString(R.string.create_room_public_description))
|
||||
switchChecked(viewState.roomType is CreateRoomViewState.RoomType.Public)
|
||||
showDivider(viewState.roomType !is CreateRoomViewState.RoomType.Public)
|
||||
switchChecked(viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public)
|
||||
showDivider(viewState.roomVisibilityType !is CreateRoomViewState.RoomVisibilityType.Public)
|
||||
|
||||
listener { value ->
|
||||
listener?.setIsPublic(value)
|
||||
}
|
||||
}
|
||||
if (viewState.roomType is CreateRoomViewState.RoomType.Public) {
|
||||
if (viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) {
|
||||
// Room alias for public room
|
||||
roomAliasEditItem {
|
||||
id("alias")
|
||||
enabled(enableFormElement)
|
||||
value(viewState.roomType.aliasLocalPart)
|
||||
value(viewState.roomVisibilityType.aliasLocalPart)
|
||||
homeServer(":" + viewState.homeServerName)
|
||||
errorMessage(
|
||||
roomAliasErrorFormatter.format(
|
||||
|
|
|
@ -76,7 +76,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
|
||||
setState {
|
||||
copy(
|
||||
isEncrypted = roomType is CreateRoomViewState.RoomType.Private && adminE2EByDefault,
|
||||
isEncrypted = roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Private && adminE2EByDefault,
|
||||
hsAdminHasDisabledE2E = !adminE2EByDefault
|
||||
)
|
||||
}
|
||||
|
@ -147,14 +147,14 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState {
|
||||
if (action.isPublic) {
|
||||
copy(
|
||||
roomType = CreateRoomViewState.RoomType.Public(""),
|
||||
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(""),
|
||||
// Reset any error in the form about alias
|
||||
asyncCreateRoomRequest = Uninitialized,
|
||||
isEncrypted = false
|
||||
)
|
||||
} else {
|
||||
copy(
|
||||
roomType = CreateRoomViewState.RoomType.Private,
|
||||
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Private,
|
||||
isEncrypted = adminE2EByDefault
|
||||
)
|
||||
}
|
||||
|
@ -162,10 +162,10 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
|
||||
private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) {
|
||||
withState { state ->
|
||||
if (state.roomType is CreateRoomViewState.RoomType.Public) {
|
||||
if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) {
|
||||
setState {
|
||||
copy(
|
||||
roomType = CreateRoomViewState.RoomType.Public(action.aliasLocalPart),
|
||||
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(action.aliasLocalPart),
|
||||
// Reset any error in the form about alias
|
||||
asyncCreateRoomRequest = Uninitialized
|
||||
)
|
||||
|
@ -191,15 +191,15 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
name = state.roomName.takeIf { it.isNotBlank() }
|
||||
topic = state.roomTopic.takeIf { it.isNotBlank() }
|
||||
avatarUri = state.avatarUri
|
||||
when (state.roomType) {
|
||||
is CreateRoomViewState.RoomType.Public -> {
|
||||
when (state.roomVisibilityType) {
|
||||
is CreateRoomViewState.RoomVisibilityType.Public -> {
|
||||
// Directory visibility
|
||||
visibility = RoomDirectoryVisibility.PUBLIC
|
||||
// Preset
|
||||
preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
roomAliasName = state.roomType.aliasLocalPart
|
||||
roomAliasName = state.roomVisibilityType.aliasLocalPart
|
||||
}
|
||||
is CreateRoomViewState.RoomType.Private -> {
|
||||
is CreateRoomViewState.RoomVisibilityType.Private -> {
|
||||
// Directory visibility
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
// Preset
|
||||
|
|
|
@ -26,7 +26,7 @@ data class CreateRoomViewState(
|
|||
val avatarUri: Uri? = null,
|
||||
val roomName: String = "",
|
||||
val roomTopic: String = "",
|
||||
val roomType: RoomType = RoomType.Private,
|
||||
val roomVisibilityType: RoomVisibilityType = RoomVisibilityType.Private,
|
||||
val isEncrypted: Boolean = false,
|
||||
val showAdvanced: Boolean = false,
|
||||
val disableFederation: Boolean = false,
|
||||
|
@ -45,10 +45,10 @@ data class CreateRoomViewState(
|
|||
fun isEmpty() = avatarUri == null
|
||||
&& roomName.isEmpty()
|
||||
&& roomTopic.isEmpty()
|
||||
&& (roomType as? RoomType.Public)?.aliasLocalPart?.isEmpty().orTrue()
|
||||
&& (roomVisibilityType as? RoomVisibilityType.Public)?.aliasLocalPart?.isEmpty().orTrue()
|
||||
|
||||
sealed class RoomType {
|
||||
object Private : RoomType()
|
||||
data class Public(val aliasLocalPart: String) : RoomType()
|
||||
sealed class RoomVisibilityType {
|
||||
object Private : RoomVisibilityType()
|
||||
data class Public(val aliasLocalPart: String) : RoomVisibilityType()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,6 +156,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
|
||||
private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY"
|
||||
|
||||
const val SETTINGS_LABS_USE_SPACES = "SETTINGS_LABS_USE_SPACES"
|
||||
|
||||
// SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS
|
||||
private const val SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM = "SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM"
|
||||
const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
|
||||
|
@ -306,6 +308,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false)
|
||||
}
|
||||
|
||||
fun labSpaces(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_USE_SPACES, false)
|
||||
}
|
||||
|
||||
fun failFast(): Boolean {
|
||||
return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.spaces
|
||||
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.grouplist.SelectedSpaceDataSource
|
||||
import im.vector.app.features.grouplist.SpaceListFragment
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
|
||||
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
|
||||
|
||||
sealed class SpaceListAction : VectorViewModelAction {
|
||||
data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Transient events for group list screen
|
||||
*/
|
||||
sealed class SpaceListViewEvents : VectorViewEvents {
|
||||
object OpenSpaceSummary : SpaceListViewEvents()
|
||||
}
|
||||
|
||||
data class SpaceListViewState(
|
||||
val asyncSpaces: Async<List<SpaceSummary>> = Uninitialized,
|
||||
val selectedSpace: SpaceSummary? = null
|
||||
) : MvRxState
|
||||
|
||||
class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState,
|
||||
private val selectedSpaceDataSource: SelectedSpaceDataSource,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider
|
||||
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: SpaceListViewState): SpacesListViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SpacesListViewModel, SpaceListViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: SpaceListViewState): SpacesListViewModel {
|
||||
val groupListFragment: SpaceListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return groupListFragment.spaceListViewModelFactory.create(state)
|
||||
}
|
||||
}
|
||||
|
||||
private var currentGroupId = ""
|
||||
|
||||
init {
|
||||
observeGroupSummaries()
|
||||
observeSelectionState()
|
||||
}
|
||||
|
||||
private fun observeSelectionState() {
|
||||
selectSubscribe(SpaceListViewState::selectedSpace) { spaceSummary ->
|
||||
if (spaceSummary != null) {
|
||||
// We only want to open group if the updated selectedGroup is a different one.
|
||||
if (currentGroupId != spaceSummary.spaceId) {
|
||||
currentGroupId = spaceSummary.spaceId
|
||||
_viewEvents.post(SpaceListViewEvents.OpenSpaceSummary)
|
||||
}
|
||||
val optionGroup = Option.just(spaceSummary)
|
||||
selectedSpaceDataSource.post(optionGroup)
|
||||
} else {
|
||||
// If selected group is null we force to default. It can happens when leaving the selected group.
|
||||
setState {
|
||||
copy(selectedSpace = this.asyncSpaces()?.find { it.spaceId == ALL_COMMUNITIES_GROUP_ID })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: SpaceListAction) {
|
||||
when (action) {
|
||||
is SpaceListAction.SelectSpace -> handleSelectSpace(action)
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
|
||||
if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) {
|
||||
// We take care of refreshing group data when selecting to be sure we get all the rooms and users
|
||||
// tryOrNull {
|
||||
// viewModelScope.launch {
|
||||
// session.getGroup(action.spaceSummary.groupId)?.fetchGroupData()
|
||||
// }
|
||||
// }
|
||||
setState { copy(selectedSpace = action.spaceSummary) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeGroupSummaries() {
|
||||
val roomSummaryQueryParams = roomSummaryQueryParams() {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
displayName = QueryStringValue.IsNotEmpty
|
||||
excludeType = listOf(RoomType.MESSAGING, null)
|
||||
}
|
||||
Observable.combineLatest<SpaceSummary, List<SpaceSummary>, List<SpaceSummary>>(
|
||||
session
|
||||
.rx()
|
||||
.liveUser(session.myUserId)
|
||||
.map { optionalUser ->
|
||||
SpaceSummary(
|
||||
spaceId = ALL_COMMUNITIES_GROUP_ID,
|
||||
roomSummary = RoomSummary(
|
||||
roomId = ALL_COMMUNITIES_GROUP_ID,
|
||||
membership = Membership.JOIN,
|
||||
displayName = stringProvider.getString(R.string.group_all_communities),
|
||||
avatarUrl = optionalUser.getOrNull()?.avatarUrl ?: "",
|
||||
encryptionEventTs = 0,
|
||||
isEncrypted = false,
|
||||
typingUsers = emptyList()
|
||||
),
|
||||
children = emptyList()
|
||||
)
|
||||
},
|
||||
session
|
||||
.rx()
|
||||
.liveSpaceSummaries(roomSummaryQueryParams),
|
||||
BiFunction { allCommunityGroup, communityGroups ->
|
||||
listOf(allCommunityGroup) + communityGroups
|
||||
}
|
||||
)
|
||||
.execute { async ->
|
||||
val currentSelectedGroupId = selectedSpace?.spaceId
|
||||
val newSelectedGroup = if (currentSelectedGroupId != null) {
|
||||
async()?.find { it.spaceId == currentSelectedGroupId }
|
||||
} else {
|
||||
async()?.firstOrNull()
|
||||
}
|
||||
copy(asyncSpaces = async, selectedSpace = newSelectedGroup)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<item android:state_checked="true">
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<solid android:color="?colorAccent" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
|
27
vector/src/main/res/drawable/bg_space_item.xml
Normal file
27
vector/src/main/res/drawable/bg_space_item.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_checked="true">
|
||||
<layer-list>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="center_vertical|start" >
|
||||
<shape>
|
||||
<size android:width="4dp" android:height="40dp" />
|
||||
<solid android:color="?colorAccent" />
|
||||
<corners android:bottomRightRadius="8dp" android:topRightRadius="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
9
vector/src/main/res/drawable/ic_selected_community.xml
Normal file
9
vector/src/main/res/drawable/ic_selected_community.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="4dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="4"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M0,0C2.2091,0 4,1.7909 4,4V28C4,30.2091 2.2091,32 0,32V0Z"
|
||||
android:fillColor="#0DBD8B"/>
|
||||
</vector>
|
12
vector/src/main/res/drawable/ic_space_home.xml
Normal file
12
vector/src/main/res/drawable/ic_space_home.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M12.75,21.62L12.75,15C12.75,14.4477 12.3023,14 11.75,14L6.25,14C5.6977,14 5.25,14.4477 5.25,15L5.25,21.6047C4.1731,21.6088 3.1268,21.6166 2.1586,21.6292C1.0345,21.6438 0.111,20.7402 0.111,19.616L0.111,8.5055C0.111,8.1143 0.2828,7.7429 0.5808,7.4896L8.0284,1.1591C8.5886,0.683 9.4112,0.683 9.9714,1.1591L17.419,7.4896C17.717,7.7429 17.8888,8.1119 17.8888,8.5031L17.8888,19.6452C17.8888,20.7595 16.981,21.6557 15.8667,21.6451C15.0172,21.637 13.9473,21.6278 12.75,21.62Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#3F3D3D"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
13
vector/src/main/res/drawable/space_home_background.xml
Normal file
13
vector/src/main/res/drawable/space_home_background.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<size android:width="40dp" android:height="40dp"/>
|
||||
|
||||
<solid android:color="?riotx_background" />
|
||||
|
||||
<stroke android:width="1dp" android:color="@color/riotx_disabled_accent"/>
|
||||
|
||||
<corners android:radius="8dp" />
|
||||
|
||||
</shape>
|
|
@ -22,24 +22,49 @@
|
|||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/groupToolbarAvatarImageView"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:contentDescription="@string/a11y_open_drawer"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/groupToolbarTitleView"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/groupToolbarTitleView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/groupToolbarSpaceTitleView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="14sp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -60,10 +85,10 @@
|
|||
android:background="?riotx_keys_backup_banner_accent_color"
|
||||
android:minHeight="67dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/syncStateView" />
|
||||
app:layout_constraintTop_toBottomOf="@id/syncStateView"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<im.vector.app.core.ui.views.CurrentCallsView
|
||||
android:id="@+id/activeCallView"
|
||||
|
|
63
vector/src/main/res/layout/item_space.xml
Normal file
63
vector/src/main/res/layout/item_space.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?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">
|
||||
|
||||
<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"
|
||||
app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/groupNameView"
|
||||
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="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
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:src="@drawable/ic_arrow_right"
|
||||
app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?riotx_text_primary"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
<View
|
||||
android:id="@+id/groupBottomSeparator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="?riotx_header_panel_border_mobile"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</im.vector.app.core.platform.CheckableConstraintLayout>
|
|
@ -2183,6 +2183,8 @@
|
|||
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
|
||||
<string name="labs_show_unread_notifications_as_tab">Add a dedicated tab for unread notifications on main screen.</string>
|
||||
|
||||
<string name="labs_experimental_spaces">Enable Spaces (formerly known as ‘groups as rooms’) to allow users to organise rooms into more useful groups.</string>
|
||||
|
||||
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
|
||||
|
||||
<string name="add_by_matrix_id">Add by matrix ID</string>
|
||||
|
@ -3242,6 +3244,8 @@
|
|||
<string name="dev_tools_success_state_event">State event sent!</string>
|
||||
<string name="dev_tools_event_content_hint">Event content</string>
|
||||
|
||||
<string name="command_description_create_space">Create a community</string>
|
||||
|
||||
<string name="event_status_a11y_sending">Sending</string>
|
||||
<string name="event_status_a11y_sent">Sent</string>
|
||||
<string name="event_status_a11y_failed">Failed</string>
|
||||
|
|
|
@ -45,4 +45,10 @@
|
|||
android:title="@string/labs_show_unread_notifications_as_tab" />
|
||||
<!--</im.vector.app.core.preference.VectorPreferenceCategory>-->
|
||||
|
||||
|
||||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="SETTINGS_LABS_USE_SPACES"
|
||||
android:title="@string/labs_experimental_spaces" />
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
Add table
Reference in a new issue