Merge branch 'vector-im/develop' into unifiedpush
This commit is contained in:
commit
88cfae9e09
@ -51,12 +51,12 @@ data class SsoIdentityProvider(
|
|||||||
) : Parcelable, Comparable<SsoIdentityProvider> {
|
) : Parcelable, Comparable<SsoIdentityProvider> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BRAND_GOOGLE = "org.matrix.google"
|
const val BRAND_GOOGLE = "google"
|
||||||
const val BRAND_GITHUB = "org.matrix.github"
|
const val BRAND_GITHUB = "github"
|
||||||
const val BRAND_APPLE = "org.matrix.apple"
|
const val BRAND_APPLE = "apple"
|
||||||
const val BRAND_FACEBOOK = "org.matrix.facebook"
|
const val BRAND_FACEBOOK = "facebook"
|
||||||
const val BRAND_TWITTER = "org.matrix.twitter"
|
const val BRAND_TWITTER = "twitter"
|
||||||
const val BRAND_GITLAB = "org.matrix.gitlab"
|
const val BRAND_GITLAB = "gitlab"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: SsoIdentityProvider): Int {
|
override fun compareTo(other: SsoIdentityProvider): Int {
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.call
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
object CallIdGenerator {
|
||||||
|
fun generate() = UUID.randomUUID().toString()
|
||||||
|
}
|
@ -26,8 +26,12 @@ interface MxCallDetail {
|
|||||||
val callId: String
|
val callId: String
|
||||||
val isOutgoing: Boolean
|
val isOutgoing: Boolean
|
||||||
val roomId: String
|
val roomId: String
|
||||||
val opponentUserId: String
|
|
||||||
val isVideoCall: Boolean
|
val isVideoCall: Boolean
|
||||||
|
val ourPartyId: String
|
||||||
|
val opponentPartyId: Optional<String>?
|
||||||
|
val opponentVersion: Int
|
||||||
|
val opponentUserId: String
|
||||||
|
val capabilities: CallCapabilities?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,12 +43,6 @@ interface MxCall : MxCallDetail {
|
|||||||
const val VOIP_PROTO_VERSION = 1
|
const val VOIP_PROTO_VERSION = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val ourPartyId: String
|
|
||||||
var opponentPartyId: Optional<String>?
|
|
||||||
var opponentVersion: Int
|
|
||||||
|
|
||||||
var capabilities: CallCapabilities?
|
|
||||||
|
|
||||||
var state: CallState
|
var state: CallState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,8 +89,12 @@ interface MxCall : MxCallDetail {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a m.call.replaces event to initiate call transfer.
|
* Send a m.call.replaces event to initiate call transfer.
|
||||||
|
* See [org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent] for documentation about the parameters
|
||||||
*/
|
*/
|
||||||
suspend fun transfer(targetUserId: String, targetRoomId: String?)
|
suspend fun transfer(targetUserId: String,
|
||||||
|
targetRoomId: String?,
|
||||||
|
createCallId: String?,
|
||||||
|
awaitCallId: String?)
|
||||||
|
|
||||||
fun addListener(listener: StateListener)
|
fun addListener(listener: StateListener)
|
||||||
fun removeListener(listener: StateListener)
|
fun removeListener(listener: StateListener)
|
||||||
|
@ -54,7 +54,7 @@ interface PermalinkService {
|
|||||||
*
|
*
|
||||||
* @return the permalink, or null in case of error
|
* @return the permalink, or null in case of error
|
||||||
*/
|
*/
|
||||||
fun createRoomPermalink(roomId: String): String?
|
fun createRoomPermalink(roomId: String, viaServers: List<String>? = null): String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a permalink for an event. If you have an event you can use [createPermalink]
|
* Creates a permalink for an event. If you have an event you can use [createPermalink]
|
||||||
|
@ -32,5 +32,7 @@ data class SpaceChildInfo(
|
|||||||
val parentRoomId: String?,
|
val parentRoomId: String?,
|
||||||
val suggested: Boolean?,
|
val suggested: Boolean?,
|
||||||
val canonicalAlias: String?,
|
val canonicalAlias: String?,
|
||||||
val aliases: List<String>?
|
val aliases: List<String>?,
|
||||||
|
val worldReadable: Boolean
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -56,6 +56,9 @@ data class CallHangupContent(
|
|||||||
@Json(name = "user_hangup")
|
@Json(name = "user_hangup")
|
||||||
USER_HANGUP,
|
USER_HANGUP,
|
||||||
|
|
||||||
|
@Json(name = "replaced")
|
||||||
|
REPLACED,
|
||||||
|
|
||||||
@Json(name = "user_media_failed")
|
@Json(name = "user_media_failed")
|
||||||
USER_MEDIA_FAILED,
|
USER_MEDIA_FAILED,
|
||||||
|
|
||||||
|
@ -38,23 +38,23 @@ data class CallReplacesContent(
|
|||||||
*/
|
*/
|
||||||
@Json(name = "replacement_id") val replacementId: String? = null,
|
@Json(name = "replacement_id") val replacementId: String? = null,
|
||||||
/**
|
/**
|
||||||
* Optional. If specified, the transferee client waits for an invite to this room and joins it
|
* Optional. If specified, the transferee client waits for an invite to this room and joins it
|
||||||
* (possibly waiting for user confirmation) and then continues the transfer in this room.
|
* (possibly waiting for user confirmation) and then continues the transfer in this room.
|
||||||
* If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
|
* If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
|
||||||
*/
|
*/
|
||||||
@Json(name = "target_room") val targerRoomId: String? = null,
|
@Json(name = "target_room") val targetRoomId: String? = null,
|
||||||
/**
|
/**
|
||||||
* An object giving information about the transfer target
|
* An object giving information about the transfer target
|
||||||
*/
|
*/
|
||||||
@Json(name = "target_user") val targetUser: TargetUser? = null,
|
@Json(name = "target_user") val targetUser: TargetUser? = null,
|
||||||
/**
|
/**
|
||||||
* If specified, gives the call ID for the transferee's client to use when placing the replacement call.
|
* If specified, gives the call ID for the transferee's client to use when placing the replacement call.
|
||||||
* Mutually exclusive with await_call
|
* Mutually exclusive with await_call
|
||||||
*/
|
*/
|
||||||
@Json(name = "create_call") val createCall: String? = null,
|
@Json(name = "create_call") val createCall: String? = null,
|
||||||
/**
|
/**
|
||||||
* If specified, gives the call ID that the transferee's client should wait for.
|
* If specified, gives the call ID that the transferee's client should wait for.
|
||||||
* Mutually exclusive with create_call.
|
* Mutually exclusive with create_call.
|
||||||
*/
|
*/
|
||||||
@Json(name = "await_call") val awaitCall: String? = null,
|
@Json(name = "await_call") val awaitCall: String? = null,
|
||||||
/**
|
/**
|
||||||
@ -77,6 +77,5 @@ data class CallReplacesContent(
|
|||||||
* Optional. The avatar URL of the transfer target.
|
* Optional. The avatar URL of the transfer target.
|
||||||
*/
|
*/
|
||||||
@Json(name = "avatar_url") val avatarUrl: String?
|
@Json(name = "avatar_url") val avatarUrl: String?
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -35,5 +35,5 @@ object MessageType {
|
|||||||
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
||||||
|
|
||||||
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
||||||
const val MSGTYPE_SNOW = "io.element.effect.snowfall"
|
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,8 @@ sealed class PeekResult {
|
|||||||
val numJoinedMembers: Int?,
|
val numJoinedMembers: Int?,
|
||||||
val roomType: String?,
|
val roomType: String?,
|
||||||
val viaServers: List<String>,
|
val viaServers: List<String>,
|
||||||
val someMembers: List<MatrixItem.UserItem>?
|
val someMembers: List<MatrixItem.UserItem>?,
|
||||||
|
val isPublic: Boolean
|
||||||
) : PeekResult()
|
) : PeekResult()
|
||||||
|
|
||||||
data class PeekingNotAllowed(
|
data class PeekingNotAllowed(
|
||||||
|
@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.util.JsonDict
|
|||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ThirdPartyUser(
|
data class ThirdPartyUser(
|
||||||
/*
|
/**
|
||||||
Required. A Matrix User ID represting a third party user.
|
* Required. A Matrix User ID representing a third party user.
|
||||||
*/
|
*/
|
||||||
@Json(name = "userid") val userId: String,
|
@Json(name = "userid") val userId: String,
|
||||||
/*
|
/**
|
||||||
Required. The protocol ID that the third party location is a part of.
|
* Required. The protocol ID that the third party location is a part of.
|
||||||
*/
|
*/
|
||||||
@Json(name = "protocol") val protocol: String,
|
@Json(name = "protocol") val protocol: String,
|
||||||
/*
|
/**
|
||||||
Required. Information used to identify this third party location.
|
* Required. Information used to identify this third party location.
|
||||||
*/
|
*/
|
||||||
@Json(name = "fields") val fields: JsonDict
|
@Json(name = "fields") val fields: JsonDict
|
||||||
)
|
)
|
||||||
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
|
|||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
@ -38,6 +39,8 @@ sealed class MatrixItem(
|
|||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class EventItem(override val id: String,
|
data class EventItem(override val id: String,
|
||||||
@ -47,6 +50,8 @@ sealed class MatrixItem(
|
|||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoomItem(override val id: String,
|
data class RoomItem(override val id: String,
|
||||||
@ -56,6 +61,19 @@ sealed class MatrixItem(
|
|||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SpaceItem(override val id: String,
|
||||||
|
override val displayName: String? = null,
|
||||||
|
override val avatarUrl: String? = null)
|
||||||
|
: MatrixItem(id, displayName, avatarUrl) {
|
||||||
|
init {
|
||||||
|
if (BuildConfig.DEBUG) checkId()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoomAliasItem(override val id: String,
|
data class RoomAliasItem(override val id: String,
|
||||||
@ -68,6 +86,8 @@ sealed class MatrixItem(
|
|||||||
|
|
||||||
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
override fun getBestName() = id
|
override fun getBestName() = id
|
||||||
|
|
||||||
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GroupItem(override val id: String,
|
data class GroupItem(override val id: String,
|
||||||
@ -80,6 +100,8 @@ sealed class MatrixItem(
|
|||||||
|
|
||||||
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
override fun getBestName() = id
|
override fun getBestName() = id
|
||||||
|
|
||||||
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getBestName(): String {
|
open fun getBestName(): String {
|
||||||
@ -92,12 +114,15 @@ sealed class MatrixItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract fun updateAvatar(newAvatar: String?): MatrixItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the prefix as defined in the matrix spec (and not extracted from the id)
|
* Return the prefix as defined in the matrix spec (and not extracted from the id)
|
||||||
*/
|
*/
|
||||||
fun getIdPrefix() = when (this) {
|
fun getIdPrefix() = when (this) {
|
||||||
is UserItem -> '@'
|
is UserItem -> '@'
|
||||||
is EventItem -> '$'
|
is EventItem -> '$'
|
||||||
|
is SpaceItem,
|
||||||
is RoomItem -> '!'
|
is RoomItem -> '!'
|
||||||
is RoomAliasItem -> '#'
|
is RoomAliasItem -> '#'
|
||||||
is GroupItem -> '+'
|
is GroupItem -> '+'
|
||||||
@ -148,7 +173,11 @@ fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
|||||||
|
|
||||||
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
||||||
|
|
||||||
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
|
||||||
|
MatrixItem.SpaceItem(roomId, displayName, avatarUrl)
|
||||||
|
} else {
|
||||||
|
MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
|
}
|
||||||
|
|
||||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||||
|
|
||||||
@ -159,4 +188,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
|
|||||||
|
|
||||||
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
||||||
|
|
||||||
fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias ?: "", avatarUrl)
|
fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
|
||||||
|
MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)
|
||||||
|
} else {
|
||||||
|
MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl)
|
||||||
|
}
|
||||||
|
@ -33,7 +33,6 @@ internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
|||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||||
*/
|
*/
|
||||||
internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||||
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
|
||||||
|
|
||||||
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||||
|
|
||||||
|
@ -88,11 +88,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||||||
|
|
||||||
return buildString {
|
return buildString {
|
||||||
append(homeServerUrlBase)
|
append(homeServerUrlBase)
|
||||||
|
append(SSO_REDIRECT_PATH)
|
||||||
if (providerId != null) {
|
if (providerId != null) {
|
||||||
append(MSC2858_SSO_REDIRECT_PATH)
|
|
||||||
append("/$providerId")
|
append("/$providerId")
|
||||||
} else {
|
|
||||||
append(SSO_REDIRECT_PATH)
|
|
||||||
}
|
}
|
||||||
// Set the redirect url
|
// Set the redirect url
|
||||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
|
appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
|
||||||
|
@ -42,7 +42,7 @@ internal data class LoginFlow(
|
|||||||
* the client can show a button for each of the supported providers
|
* the client can show a button for each of the supported providers
|
||||||
* See MSC #2858
|
* See MSC #2858
|
||||||
*/
|
*/
|
||||||
@Json(name = "org.matrix.msc2858.identity_providers")
|
@Json(name = "identity_providers")
|
||||||
val ssoIdentityProvider: List<SsoIdentityProvider>? = null
|
val ssoIdentityProvider: List<SsoIdentityProvider>? = null
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database.mapper
|
package org.matrix.android.sdk.internal.database.mapper
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
|
||||||
@ -92,7 +93,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||||||
parentRoomId = roomSummaryEntity.roomId,
|
parentRoomId = roomSummaryEntity.roomId,
|
||||||
suggested = it.suggested,
|
suggested = it.suggested,
|
||||||
canonicalAlias = it.childSummaryEntity?.canonicalAlias,
|
canonicalAlias = it.childSummaryEntity?.canonicalAlias,
|
||||||
aliases = it.childSummaryEntity?.aliases?.toList()
|
aliases = it.childSummaryEntity?.aliases?.toList(),
|
||||||
|
worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
|
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
|
||||||
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
|||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
@ -43,15 +44,16 @@ internal class SessionListeners @Inject constructor(
|
|||||||
|
|
||||||
fun dispatch(block: (Session, Session.Listener) -> Unit) {
|
fun dispatch(block: (Session, Session.Listener) -> Unit) {
|
||||||
synchronized(listeners) {
|
synchronized(listeners) {
|
||||||
val session = getSession()
|
val session = getSession() ?: return Unit.also {
|
||||||
|
Timber.w("You don't have any attached session")
|
||||||
|
}
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
tryOrNull { block(session, it) }
|
tryOrNull { block(session, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSession(): Session {
|
private fun getSession(): Session? {
|
||||||
return sessionManager.getSessionComponent(sessionId)?.session()
|
return sessionManager.getSessionComponent(sessionId)?.session()
|
||||||
?: throw IllegalStateException("No session found with this id.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,18 +24,15 @@ 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.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.math.BigDecimal
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
@ -192,6 +189,9 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
|
|||||||
// Ignore remote echo
|
// Ignore remote echo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (event.roomId == null || event.senderId == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (event.senderId == userId) {
|
if (event.senderId == userId) {
|
||||||
// discard current call, it's answered by another of my session
|
// discard current call, it's answered by another of my session
|
||||||
activeCallHandler.removeCall(call.callId)
|
activeCallHandler.removeCall(call.callId)
|
||||||
@ -201,11 +201,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
|
|||||||
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
|
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
call.apply {
|
mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
|
||||||
opponentPartyId = Optional.from(content.partyId)
|
|
||||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
|
||||||
capabilities = content.capabilities ?: CallCapabilities()
|
|
||||||
}
|
|
||||||
callListenersDispatcher.onCallAnswerReceived(content)
|
callListenersDispatcher.onCallAnswerReceived(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,17 @@
|
|||||||
package org.matrix.android.sdk.internal.session.call
|
package org.matrix.android.sdk.internal.session.call
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
|
import org.matrix.android.sdk.api.session.call.CallIdGenerator
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
|
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
||||||
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
|
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class MxCallFactory @Inject constructor(
|
internal class MxCallFactory @Inject constructor(
|
||||||
@ -48,32 +47,38 @@ internal class MxCallFactory @Inject constructor(
|
|||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
ourPartyId = deviceId ?: "",
|
ourPartyId = deviceId ?: "",
|
||||||
opponentUserId = opponentUserId,
|
|
||||||
isVideoCall = content.isVideo(),
|
isVideoCall = content.isVideo(),
|
||||||
localEchoEventFactory = localEchoEventFactory,
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
eventSenderProcessor = eventSenderProcessor,
|
eventSenderProcessor = eventSenderProcessor,
|
||||||
matrixConfiguration = matrixConfiguration,
|
matrixConfiguration = matrixConfiguration,
|
||||||
getProfileInfoTask = getProfileInfoTask
|
getProfileInfoTask = getProfileInfoTask
|
||||||
).apply {
|
).apply {
|
||||||
opponentPartyId = Optional.from(content.partyId)
|
updateOpponentData(opponentUserId, content, content.capabilities)
|
||||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
|
||||||
capabilities = content.capabilities ?: CallCapabilities()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall {
|
fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall {
|
||||||
return MxCallImpl(
|
return MxCallImpl(
|
||||||
callId = UUID.randomUUID().toString(),
|
callId = CallIdGenerator.generate(),
|
||||||
isOutgoing = true,
|
isOutgoing = true,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
ourPartyId = deviceId ?: "",
|
ourPartyId = deviceId ?: "",
|
||||||
opponentUserId = opponentUserId,
|
|
||||||
isVideoCall = isVideoCall,
|
isVideoCall = isVideoCall,
|
||||||
localEchoEventFactory = localEchoEventFactory,
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
eventSenderProcessor = eventSenderProcessor,
|
eventSenderProcessor = eventSenderProcessor,
|
||||||
matrixConfiguration = matrixConfiguration,
|
matrixConfiguration = matrixConfiguration,
|
||||||
getProfileInfoTask = getProfileInfoTask
|
getProfileInfoTask = getProfileInfoTask
|
||||||
)
|
).apply {
|
||||||
|
// Setup with this userId, might be updated when processing the Answer event
|
||||||
|
this.opponentUserId = opponentUserId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateOutgoingCallWithOpponentData(call: MxCall,
|
||||||
|
userId: String,
|
||||||
|
content: CallSignalingContent,
|
||||||
|
callCapabilities: CallCapabilities?) {
|
||||||
|
(call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.matrix.android.sdk.internal.session.call.model
|
package org.matrix.android.sdk.internal.session.call.model
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
|
import org.matrix.android.sdk.api.session.call.CallIdGenerator
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
@ -36,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
|||||||
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.SdpType
|
import org.matrix.android.sdk.api.session.room.model.call.SdpType
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
||||||
@ -43,14 +45,13 @@ import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
|
|||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.math.BigDecimal
|
||||||
|
|
||||||
internal class MxCallImpl(
|
internal class MxCallImpl(
|
||||||
override val callId: String,
|
override val callId: String,
|
||||||
override val isOutgoing: Boolean,
|
override val isOutgoing: Boolean,
|
||||||
override val roomId: String,
|
override val roomId: String,
|
||||||
private val userId: String,
|
private val userId: String,
|
||||||
override val opponentUserId: String,
|
|
||||||
override val isVideoCall: Boolean,
|
override val isVideoCall: Boolean,
|
||||||
override val ourPartyId: String,
|
override val ourPartyId: String,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
@ -61,8 +62,16 @@ internal class MxCallImpl(
|
|||||||
|
|
||||||
override var opponentPartyId: Optional<String>? = null
|
override var opponentPartyId: Optional<String>? = null
|
||||||
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
|
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
|
||||||
|
override lateinit var opponentUserId: String
|
||||||
override var capabilities: CallCapabilities? = null
|
override var capabilities: CallCapabilities? = null
|
||||||
|
|
||||||
|
fun updateOpponentData(userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) {
|
||||||
|
opponentPartyId = Optional.from(content.partyId)
|
||||||
|
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||||
|
opponentUserId = userId
|
||||||
|
capabilities = callCapabilities ?: CallCapabilities()
|
||||||
|
}
|
||||||
|
|
||||||
override var state: CallState = CallState.Idle
|
override var state: CallState = CallState.Idle
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@ -202,7 +211,10 @@ internal class MxCallImpl(
|
|||||||
.also { eventSenderProcessor.postEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun transfer(targetUserId: String, targetRoomId: String?) {
|
override suspend fun transfer(targetUserId: String,
|
||||||
|
targetRoomId: String?,
|
||||||
|
createCallId: String?,
|
||||||
|
awaitCallId: String?) {
|
||||||
val profileInfoParams = GetProfileInfoTask.Params(targetUserId)
|
val profileInfoParams = GetProfileInfoTask.Params(targetUserId)
|
||||||
val profileInfo = try {
|
val profileInfo = try {
|
||||||
getProfileInfoTask.execute(profileInfoParams)
|
getProfileInfoTask.execute(profileInfoParams)
|
||||||
@ -213,15 +225,16 @@ internal class MxCallImpl(
|
|||||||
CallReplacesContent(
|
CallReplacesContent(
|
||||||
callId = callId,
|
callId = callId,
|
||||||
partyId = ourPartyId,
|
partyId = ourPartyId,
|
||||||
replacementId = UUID.randomUUID().toString(),
|
replacementId = CallIdGenerator.generate(),
|
||||||
version = MxCall.VOIP_PROTO_VERSION.toString(),
|
version = MxCall.VOIP_PROTO_VERSION.toString(),
|
||||||
targetUser = CallReplacesContent.TargetUser(
|
targetUser = CallReplacesContent.TargetUser(
|
||||||
id = targetUserId,
|
id = targetUserId,
|
||||||
displayName = profileInfo?.get(ProfileService.DISPLAY_NAME_KEY) as? String,
|
displayName = profileInfo?.get(ProfileService.DISPLAY_NAME_KEY) as? String,
|
||||||
avatarUrl = profileInfo?.get(ProfileService.AVATAR_URL_KEY) as? String
|
avatarUrl = profileInfo?.get(ProfileService.AVATAR_URL_KEY) as? String
|
||||||
),
|
),
|
||||||
targerRoomId = targetRoomId,
|
targetRoomId = targetRoomId,
|
||||||
createCall = UUID.randomUUID().toString()
|
awaitCall = awaitCallId,
|
||||||
|
createCall = createCallId
|
||||||
)
|
)
|
||||||
.let { createEventAndLocalEcho(type = EventType.CALL_REPLACES, roomId = roomId, content = it.toContent()) }
|
.let { createEventAndLocalEcho(type = EventType.CALL_REPLACES, roomId = roomId, content = it.toContent()) }
|
||||||
.also { eventSenderProcessor.postEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
|
@ -52,7 +52,7 @@ internal class ThumbnailExtractor @Inject constructor(
|
|||||||
mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
|
mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
|
||||||
mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
|
mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
thumbnail.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
|
||||||
val thumbnailWidth = thumbnail.width
|
val thumbnailWidth = thumbnail.width
|
||||||
val thumbnailHeight = thumbnail.height
|
val thumbnailHeight = thumbnail.height
|
||||||
val thumbnailSize = outputStream.size()
|
val thumbnailSize = outputStream.size()
|
||||||
|
@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor(
|
|||||||
return permalinkFactory.createPermalink(id)
|
return permalinkFactory.createPermalink(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createRoomPermalink(roomId: String): String? {
|
override fun createRoomPermalink(roomId: String, viaServers: List<String>?): String? {
|
||||||
return permalinkFactory.createRoomPermalink(roomId)
|
return permalinkFactory.createRoomPermalink(roomId, viaServers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPermalink(roomId: String, eventId: String): String {
|
override fun createPermalink(roomId: String, eventId: String): String {
|
||||||
|
@ -40,11 +40,18 @@ internal class PermalinkFactory @Inject constructor(
|
|||||||
} else MATRIX_TO_URL_BASE + escape(id)
|
} else MATRIX_TO_URL_BASE + escape(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoomPermalink(roomId: String): String? {
|
fun createRoomPermalink(roomId: String, via: List<String>? = null): String? {
|
||||||
return if (roomId.isEmpty()) {
|
return if (roomId.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId)
|
buildString {
|
||||||
|
append(MATRIX_TO_URL_BASE)
|
||||||
|
append(escape(roomId))
|
||||||
|
append(
|
||||||
|
via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.asUrlViaParameters(it) }
|
||||||
|
?: viaParameterFinder.computeViaParams(userId, roomId)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,11 @@ internal class ViaParameterFinder @Inject constructor(
|
|||||||
* current user one.
|
* current user one.
|
||||||
*/
|
*/
|
||||||
fun computeViaParams(userId: String, roomId: String): String {
|
fun computeViaParams(userId: String, roomId: String): String {
|
||||||
return computeViaParams(userId, roomId, 3)
|
return asUrlViaParameters(computeViaParams(userId, roomId, 3))
|
||||||
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
|
}
|
||||||
|
|
||||||
|
fun asUrlViaParameters(viaList: List<String>): String {
|
||||||
|
return viaList.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {
|
fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {
|
||||||
|
@ -23,6 +23,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
|||||||
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
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.RoomHistoryVisibilityContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
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.RoomTopicContent
|
||||||
@ -105,7 +107,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||||||
numJoinedMembers = publicRepoResult.numJoinedMembers,
|
numJoinedMembers = publicRepoResult.numJoinedMembers,
|
||||||
viaServers = serverList,
|
viaServers = serverList,
|
||||||
roomType = null, // would be nice to get that from directory...
|
roomType = null, // would be nice to get that from directory...
|
||||||
someMembers = null
|
someMembers = null,
|
||||||
|
isPublic = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +146,11 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val historyVisibility =
|
||||||
|
stateEvents
|
||||||
|
.lastOrNull { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY && it.stateKey?.isNotEmpty() == true }
|
||||||
|
?.let { it.content?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility }
|
||||||
|
|
||||||
val roomType = stateEvents
|
val roomType = stateEvents
|
||||||
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
|
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
|
||||||
?.content
|
?.content
|
||||||
@ -158,7 +166,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||||||
numJoinedMembers = memberCount,
|
numJoinedMembers = memberCount,
|
||||||
roomType = roomType,
|
roomType = roomType,
|
||||||
viaServers = serverList,
|
viaServers = serverList,
|
||||||
someMembers = someMembers
|
someMembers = someMembers,
|
||||||
|
isPublic = historyVisibility == RoomHistoryVisibility.WORLD_READABLE
|
||||||
)
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// Would be M_FORBIDDEN if cannot peek :/
|
// Would be M_FORBIDDEN if cannot peek :/
|
||||||
|
@ -147,7 +147,8 @@ internal class DefaultSpaceService @Inject constructor(
|
|||||||
parentRoomId = childStateEv.roomId,
|
parentRoomId = childStateEv.roomId,
|
||||||
suggested = childStateEvContent.suggested,
|
suggested = childStateEvContent.suggested,
|
||||||
canonicalAlias = childSummary.canonicalAlias,
|
canonicalAlias = childSummary.canonicalAlias,
|
||||||
aliases = childSummary.aliases
|
aliases = childSummary.aliases,
|
||||||
|
worldReadable = childSummary.worldReadable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
|
1
newsfragment/1458.feature
Normal file
1
newsfragment/1458.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Allow user to add custom "network" in room search
|
1
newsfragment/3196.feature
Normal file
1
newsfragment/3196.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add Gitter.im as a default in the Change Network menu
|
1
newsfragment/3396.feature
Normal file
1
newsfragment/3396.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Compress thumbnail: change Jpeg quality from 100 to 80
|
1
newsfragment/3401.bugfix
Normal file
1
newsfragment/3401.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools)
|
1
newsfragment/3406.bugfix
Normal file
1
newsfragment/3406.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix | Suggest Rooms, Show a detailed view of the room on click
|
1
newsfragment/3420.feature
Normal file
1
newsfragment/3420.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
VoIP: support attended transfer
|
1
newsfragment/3424.bugfix
Normal file
1
newsfragment/3424.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix app crashing when signing out
|
1
newsfragment/3430.feature
Normal file
1
newsfragment/3430.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
/snow -> /snowfall and update wording (iso Element Web)
|
1
newsfragment/3442.bugfix
Normal file
1
newsfragment/3442.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Switch to stable endpoint/fields for MSC2858
|
@ -94,6 +94,10 @@ fun <T : Fragment> AppCompatActivity.addFragmentToBackstack(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun AppCompatActivity.popBackstack() {
|
||||||
|
supportFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
fun AppCompatActivity.resetBackstack() {
|
fun AppCompatActivity.resetBackstack() {
|
||||||
repeat(supportFragmentManager.backStackEntryCount) {
|
repeat(supportFragmentManager.backStackEntryCount) {
|
||||||
supportFragmentManager.popBackStack()
|
supportFragmentManager.popBackStack()
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.app.core.ui.list
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic item with empty space.
|
||||||
|
*/
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_vertical_margin)
|
||||||
|
abstract class VerticalMarginItem : VectorEpoxyModel<VerticalMarginItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var heightInPx: Int = 0
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.space.updateLayoutParams {
|
||||||
|
height = heightInPx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val space by bind<View>(R.id.item_vertical_margin_space)
|
||||||
|
}
|
||||||
|
}
|
@ -175,7 +175,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
when (callState) {
|
when (callState) {
|
||||||
is CallState.Idle,
|
is CallState.Idle,
|
||||||
is CallState.CreateOffer,
|
is CallState.CreateOffer,
|
||||||
is CallState.Dialing -> {
|
is CallState.Dialing -> {
|
||||||
views.callVideoGroup.isInvisible = true
|
views.callVideoGroup.isInvisible = true
|
||||||
views.callInfoGroup.isVisible = true
|
views.callInfoGroup.isVisible = true
|
||||||
views.callStatusText.setText(R.string.call_ring)
|
views.callStatusText.setText(R.string.call_ring)
|
||||||
@ -189,16 +189,27 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
configureCallInfo(state)
|
configureCallInfo(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
is CallState.Answering -> {
|
is CallState.Answering -> {
|
||||||
views.callVideoGroup.isInvisible = true
|
views.callVideoGroup.isInvisible = true
|
||||||
views.callInfoGroup.isVisible = true
|
views.callInfoGroup.isVisible = true
|
||||||
views.callStatusText.setText(R.string.call_connecting)
|
views.callStatusText.setText(R.string.call_connecting)
|
||||||
views.callConnectingProgress.isVisible = true
|
views.callConnectingProgress.isVisible = true
|
||||||
configureCallInfo(state)
|
configureCallInfo(state)
|
||||||
}
|
}
|
||||||
is CallState.Connected -> {
|
is CallState.Connected -> {
|
||||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||||
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) {
|
||||||
|
val transfereeName = if (state.transferee is VectorCallViewState.TransfereeState.KnownTransferee) {
|
||||||
|
state.transferee.name
|
||||||
|
} else {
|
||||||
|
getString(R.string.call_transfer_unknown_person)
|
||||||
|
}
|
||||||
|
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, transfereeName)
|
||||||
|
views.callActionText.isVisible = true
|
||||||
|
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) }
|
||||||
|
views.callStatusText.text = state.formattedDuration
|
||||||
|
configureCallInfo(state)
|
||||||
|
} else if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||||
views.smallIsHeldIcon.isVisible = true
|
views.smallIsHeldIcon.isVisible = true
|
||||||
views.callVideoGroup.isInvisible = true
|
views.callVideoGroup.isInvisible = true
|
||||||
views.callInfoGroup.isVisible = true
|
views.callInfoGroup.isVisible = true
|
||||||
@ -220,7 +231,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
if (callArgs.isVideoCall) {
|
if (callArgs.isVideoCall) {
|
||||||
views.callVideoGroup.isVisible = true
|
views.callVideoGroup.isVisible = true
|
||||||
views.callInfoGroup.isVisible = false
|
views.callInfoGroup.isVisible = false
|
||||||
views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null
|
views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null
|
||||||
} else {
|
} else {
|
||||||
views.callVideoGroup.isInvisible = true
|
views.callVideoGroup.isInvisible = true
|
||||||
views.callInfoGroup.isVisible = true
|
views.callInfoGroup.isVisible = true
|
||||||
@ -235,10 +246,10 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
views.callConnectingProgress.isVisible = true
|
views.callConnectingProgress.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CallState.Terminated -> {
|
is CallState.Terminated -> {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
null -> {
|
null -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,7 +258,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
state.callInfo.otherUserItem?.let {
|
state.callInfo.otherUserItem?.let {
|
||||||
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
|
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
|
||||||
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter)
|
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter)
|
||||||
views.participantNameText.text = it.getBestName()
|
if (state.transferee is VectorCallViewState.TransfereeState.NoTransferee) {
|
||||||
|
views.participantNameText.text = it.getBestName()
|
||||||
|
} else {
|
||||||
|
views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName())
|
||||||
|
}
|
||||||
if (blurAvatar) {
|
if (blurAvatar) {
|
||||||
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter)
|
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter)
|
||||||
} else {
|
} else {
|
||||||
@ -322,13 +337,13 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
private fun handleViewEvents(event: VectorCallViewEvents?) {
|
private fun handleViewEvents(event: VectorCallViewEvents?) {
|
||||||
Timber.v("## VOIP handleViewEvents $event")
|
Timber.v("## VOIP handleViewEvents $event")
|
||||||
when (event) {
|
when (event) {
|
||||||
VectorCallViewEvents.DismissNoCall -> {
|
VectorCallViewEvents.DismissNoCall -> {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
is VectorCallViewEvents.ConnectionTimeout -> {
|
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||||
onErrorTimoutConnect(event.turn)
|
onErrorTimoutConnect(event.turn)
|
||||||
}
|
}
|
||||||
is VectorCallViewEvents.ShowDialPad -> {
|
is VectorCallViewEvents.ShowDialPad -> {
|
||||||
CallDialPadBottomSheet.newInstance(false).apply {
|
CallDialPadBottomSheet.newInstance(false).apply {
|
||||||
callback = dialPadCallback
|
callback = dialPadCallback
|
||||||
}.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG)
|
}.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG)
|
||||||
@ -336,7 +351,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||||||
is VectorCallViewEvents.ShowCallTransferScreen -> {
|
is VectorCallViewEvents.ShowCallTransferScreen -> {
|
||||||
navigator.openCallTransfer(this, callArgs.callId)
|
navigator.openCallTransfer(this, callArgs.callId)
|
||||||
}
|
}
|
||||||
null -> {
|
null -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,5 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
|||||||
object ToggleCamera : VectorCallViewActions()
|
object ToggleCamera : VectorCallViewActions()
|
||||||
object ToggleHDSD : VectorCallViewActions()
|
object ToggleHDSD : VectorCallViewActions()
|
||||||
object InitiateCallTransfer : VectorCallViewActions()
|
object InitiateCallTransfer : VectorCallViewActions()
|
||||||
|
object TransferCall: VectorCallViewActions()
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,8 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.call.audio.CallAudioManager
|
import im.vector.app.features.call.audio.CallAudioManager
|
||||||
@ -111,12 +111,21 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
callState = Success(callState),
|
callState = Success(callState),
|
||||||
canOpponentBeTransferred = call.capabilities.supportCallTransfer()
|
canOpponentBeTransferred = call.capabilities.supportCallTransfer(),
|
||||||
|
transferee = computeTransfereeState(call)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun computeTransfereeState(call: MxCall): VectorCallViewState.TransfereeState {
|
||||||
|
val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return VectorCallViewState.TransfereeState.NoTransferee
|
||||||
|
val transfereeRoom = session.getRoomSummary(transfereeCall.nativeRoomId)
|
||||||
|
return transfereeRoom?.displayName?.let {
|
||||||
|
VectorCallViewState.TransfereeState.KnownTransferee(it)
|
||||||
|
} ?: VectorCallViewState.TransfereeState.UnknownTransferee
|
||||||
|
}
|
||||||
|
|
||||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
||||||
|
|
||||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||||
@ -166,7 +175,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
} else {
|
} else {
|
||||||
call = webRtcCall
|
call = webRtcCall
|
||||||
callManager.addCurrentCallListener(currentCallListener)
|
callManager.addCurrentCallListener(currentCallListener)
|
||||||
val item = webRtcCall.getOpponentAsMatrixItem(session)
|
val item = webRtcCall.getOpponentAsMatrixItem(session)
|
||||||
webRtcCall.addListener(callListener)
|
webRtcCall.addListener(callListener)
|
||||||
val currentSoundDevice = callManager.audioManager.selectedDevice
|
val currentSoundDevice = callManager.audioManager.selectedDevice
|
||||||
if (currentSoundDevice == CallAudioManager.Device.PHONE) {
|
if (currentSoundDevice == CallAudioManager.Device.PHONE) {
|
||||||
@ -185,7 +194,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
||||||
formattedDuration = webRtcCall.formattedDuration(),
|
formattedDuration = webRtcCall.formattedDuration(),
|
||||||
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD,
|
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD,
|
||||||
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer()
|
canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(),
|
||||||
|
transferee = computeTransfereeState(webRtcCall.mxCall)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateOtherKnownCall(webRtcCall)
|
updateOtherKnownCall(webRtcCall)
|
||||||
@ -201,27 +211,27 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
override fun handle(action: VectorCallViewActions) = withState { state ->
|
override fun handle(action: VectorCallViewActions) = withState { state ->
|
||||||
when (action) {
|
when (action) {
|
||||||
VectorCallViewActions.EndCall -> call?.endCall()
|
VectorCallViewActions.EndCall -> call?.endCall()
|
||||||
VectorCallViewActions.AcceptCall -> {
|
VectorCallViewActions.AcceptCall -> {
|
||||||
setState {
|
setState {
|
||||||
copy(callState = Loading())
|
copy(callState = Loading())
|
||||||
}
|
}
|
||||||
call?.acceptIncomingCall()
|
call?.acceptIncomingCall()
|
||||||
}
|
}
|
||||||
VectorCallViewActions.DeclineCall -> {
|
VectorCallViewActions.DeclineCall -> {
|
||||||
setState {
|
setState {
|
||||||
copy(callState = Loading())
|
copy(callState = Loading())
|
||||||
}
|
}
|
||||||
call?.endCall()
|
call?.endCall()
|
||||||
}
|
}
|
||||||
VectorCallViewActions.ToggleMute -> {
|
VectorCallViewActions.ToggleMute -> {
|
||||||
val muted = state.isAudioMuted
|
val muted = state.isAudioMuted
|
||||||
call?.muteCall(!muted)
|
call?.muteCall(!muted)
|
||||||
setState {
|
setState {
|
||||||
copy(isAudioMuted = !muted)
|
copy(isAudioMuted = !muted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VectorCallViewActions.ToggleVideo -> {
|
VectorCallViewActions.ToggleVideo -> {
|
||||||
if (state.isVideoCall) {
|
if (state.isVideoCall) {
|
||||||
val videoEnabled = state.isVideoEnabled
|
val videoEnabled = state.isVideoEnabled
|
||||||
call?.enableVideo(!videoEnabled)
|
call?.enableVideo(!videoEnabled)
|
||||||
@ -231,14 +241,14 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
VectorCallViewActions.ToggleHoldResume -> {
|
VectorCallViewActions.ToggleHoldResume -> {
|
||||||
val isRemoteOnHold = state.isRemoteOnHold
|
val isRemoteOnHold = state.isRemoteOnHold
|
||||||
call?.updateRemoteOnHold(!isRemoteOnHold)
|
call?.updateRemoteOnHold(!isRemoteOnHold)
|
||||||
}
|
}
|
||||||
is VectorCallViewActions.ChangeAudioDevice -> {
|
is VectorCallViewActions.ChangeAudioDevice -> {
|
||||||
callManager.audioManager.setAudioDevice(action.device)
|
callManager.audioManager.setAudioDevice(action.device)
|
||||||
}
|
}
|
||||||
VectorCallViewActions.SwitchSoundDevice -> {
|
VectorCallViewActions.SwitchSoundDevice -> {
|
||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device)
|
VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device)
|
||||||
)
|
)
|
||||||
@ -254,17 +264,17 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
VectorCallViewActions.ToggleCamera -> {
|
VectorCallViewActions.ToggleCamera -> {
|
||||||
call?.switchCamera()
|
call?.switchCamera()
|
||||||
}
|
}
|
||||||
VectorCallViewActions.ToggleHDSD -> {
|
VectorCallViewActions.ToggleHDSD -> {
|
||||||
if (!state.isVideoCall) return@withState
|
if (!state.isVideoCall) return@withState
|
||||||
call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
|
call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
|
||||||
}
|
}
|
||||||
VectorCallViewActions.OpenDialPad -> {
|
VectorCallViewActions.OpenDialPad -> {
|
||||||
_viewEvents.post(VectorCallViewEvents.ShowDialPad)
|
_viewEvents.post(VectorCallViewEvents.ShowDialPad)
|
||||||
}
|
}
|
||||||
is VectorCallViewActions.SendDtmfDigit -> {
|
is VectorCallViewActions.SendDtmfDigit -> {
|
||||||
call?.sendDtmfDigit(action.digit)
|
call?.sendDtmfDigit(action.digit)
|
||||||
}
|
}
|
||||||
VectorCallViewActions.InitiateCallTransfer -> {
|
VectorCallViewActions.InitiateCallTransfer -> {
|
||||||
@ -272,9 +282,20 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
VectorCallViewEvents.ShowCallTransferScreen
|
VectorCallViewEvents.ShowCallTransferScreen
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
VectorCallViewActions.TransferCall -> {
|
||||||
|
handleCallTransfer()
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCallTransfer() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val currentCall = call ?: return@launch
|
||||||
|
val transfereeCall = callManager.getTransfereeForCallId(currentCall.callId) ?: return@launch
|
||||||
|
currentCall.transferToCall(transfereeCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(initialState: VectorCallViewState): VectorCallViewModel
|
fun create(initialState: VectorCallViewState): VectorCallViewModel
|
||||||
|
@ -41,15 +41,22 @@ data class VectorCallViewState(
|
|||||||
val otherKnownCallInfo: CallInfo? = null,
|
val otherKnownCallInfo: CallInfo? = null,
|
||||||
val callInfo: CallInfo = CallInfo(callId),
|
val callInfo: CallInfo = CallInfo(callId),
|
||||||
val formattedDuration: String = "",
|
val formattedDuration: String = "",
|
||||||
val canOpponentBeTransferred: Boolean = false
|
val canOpponentBeTransferred: Boolean = false,
|
||||||
|
val transferee: TransfereeState = TransfereeState.NoTransferee
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
|
sealed class TransfereeState {
|
||||||
|
object NoTransferee : TransfereeState()
|
||||||
|
data class KnownTransferee(val name: String) : TransfereeState()
|
||||||
|
object UnknownTransferee : TransfereeState()
|
||||||
|
}
|
||||||
|
|
||||||
data class CallInfo(
|
data class CallInfo(
|
||||||
val callId: String,
|
val callId: String,
|
||||||
val otherUserItem: MatrixItem? = null
|
val otherUserItem: MatrixItem? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(callArgs: CallArgs): this(
|
constructor(callArgs: CallArgs) : this(
|
||||||
callId = callArgs.callId,
|
callId = callArgs.callId,
|
||||||
roomId = callArgs.signalingRoomId,
|
roomId = callArgs.signalingRoomId,
|
||||||
isVideoCall = callArgs.isVideoCall
|
isVideoCall = callArgs.isVideoCall
|
||||||
|
@ -28,13 +28,16 @@ import im.vector.app.core.platform.VectorViewModel
|
|||||||
import im.vector.app.features.call.dialpad.DialPadLookup
|
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
|
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
|
|
||||||
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
|
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
|
||||||
private val dialPadLookup: DialPadLookup,
|
private val dialPadLookup: DialPadLookup,
|
||||||
callManager: WebRtcCallManager)
|
private val directRoomHelper: DirectRoomHelper,
|
||||||
|
private val callManager: WebRtcCallManager)
|
||||||
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) {
|
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@ -75,7 +78,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
|
|
||||||
override fun handle(action: CallTransferAction) {
|
override fun handle(action: CallTransferAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is CallTransferAction.ConnectWithUserId -> connectWithUserId(action)
|
is CallTransferAction.ConnectWithUserId -> connectWithUserId(action)
|
||||||
is CallTransferAction.ConnectWithPhoneNumber -> connectWithPhoneNumber(action)
|
is CallTransferAction.ConnectWithPhoneNumber -> connectWithPhoneNumber(action)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
@ -83,8 +86,17 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) {
|
private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
_viewEvents.post(CallTransferViewEvents.Loading)
|
if (action.consultFirst) {
|
||||||
call?.mxCall?.transfer(action.selectedUserId, null)
|
val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId)
|
||||||
|
callManager.startOutgoingCall(
|
||||||
|
nativeRoomId = dmRoomId,
|
||||||
|
otherUserId = action.selectedUserId,
|
||||||
|
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
|
||||||
|
transferee = call
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
call?.transferToUser(action.selectedUserId, null)
|
||||||
|
}
|
||||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
||||||
@ -97,7 +109,16 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
try {
|
try {
|
||||||
_viewEvents.post(CallTransferViewEvents.Loading)
|
_viewEvents.post(CallTransferViewEvents.Loading)
|
||||||
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
|
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
|
||||||
call?.mxCall?.transfer(result.userId, result.roomId)
|
if (action.consultFirst) {
|
||||||
|
callManager.startOutgoingCall(
|
||||||
|
nativeRoomId = result.roomId,
|
||||||
|
otherUserId = result.userId,
|
||||||
|
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
|
||||||
|
transferee = call
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
call?.transferToUser(result.userId, result.roomId)
|
||||||
|
}
|
||||||
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
_viewEvents.post(CallTransferViewEvents.Dismiss)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
|
||||||
|
@ -45,6 +45,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.call.CallIdGenerator
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||||
@ -85,16 +86,19 @@ private const val AUDIO_TRACK_ID = "ARDAMSa0"
|
|||||||
private const val VIDEO_TRACK_ID = "ARDAMSv0"
|
private const val VIDEO_TRACK_ID = "ARDAMSv0"
|
||||||
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
|
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
|
||||||
|
|
||||||
class WebRtcCall(val mxCall: MxCall,
|
class WebRtcCall(
|
||||||
// This is where the call is placed from an ui perspective. In case of virtual room, it can differs from the signalingRoomId.
|
val mxCall: MxCall,
|
||||||
val nativeRoomId: String,
|
// This is where the call is placed from an ui perspective.
|
||||||
private val rootEglBase: EglBase?,
|
// In case of virtual room, it can differs from the signalingRoomId.
|
||||||
private val context: Context,
|
val nativeRoomId: String,
|
||||||
private val dispatcher: CoroutineContext,
|
private val rootEglBase: EglBase?,
|
||||||
private val sessionProvider: Provider<Session?>,
|
private val context: Context,
|
||||||
private val peerConnectionFactoryProvider: Provider<PeerConnectionFactory?>,
|
private val dispatcher: CoroutineContext,
|
||||||
private val onCallBecomeActive: (WebRtcCall) -> Unit,
|
private val sessionProvider: Provider<Session?>,
|
||||||
private val onCallEnded: (String) -> Unit) : MxCall.StateListener {
|
private val peerConnectionFactoryProvider: Provider<PeerConnectionFactory?>,
|
||||||
|
private val onCallBecomeActive: (WebRtcCall) -> Unit,
|
||||||
|
private val onCallEnded: (String) -> Unit
|
||||||
|
) : MxCall.StateListener {
|
||||||
|
|
||||||
interface Listener : MxCall.StateListener {
|
interface Listener : MxCall.StateListener {
|
||||||
fun onCaptureStateChanged() {}
|
fun onCaptureStateChanged() {}
|
||||||
@ -118,6 +122,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val callId = mxCall.callId
|
val callId = mxCall.callId
|
||||||
|
|
||||||
// room where call signaling is placed. In case of virtual room it can differs from the nativeRoomId.
|
// room where call signaling is placed. In case of virtual room it can differs from the nativeRoomId.
|
||||||
val signalingRoomId = mxCall.roomId
|
val signalingRoomId = mxCall.roomId
|
||||||
|
|
||||||
@ -271,7 +276,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
|
|
||||||
sessionScope?.launch(dispatcher) {
|
sessionScope?.launch(dispatcher) {
|
||||||
when (mode) {
|
when (mode) {
|
||||||
VectorCallActivity.INCOMING_ACCEPT -> {
|
VectorCallActivity.INCOMING_ACCEPT -> {
|
||||||
internalAcceptIncomingCall()
|
internalAcceptIncomingCall()
|
||||||
}
|
}
|
||||||
VectorCallActivity.INCOMING_RINGING -> {
|
VectorCallActivity.INCOMING_RINGING -> {
|
||||||
@ -289,6 +294,40 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Without consultation
|
||||||
|
*/
|
||||||
|
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) {
|
||||||
|
mxCall.transfer(
|
||||||
|
targetUserId = targetUserId,
|
||||||
|
targetRoomId = targetRoomId,
|
||||||
|
createCallId = CallIdGenerator.generate(),
|
||||||
|
awaitCallId = null
|
||||||
|
)
|
||||||
|
endCall(sendEndSignaling = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With consultation
|
||||||
|
*/
|
||||||
|
suspend fun transferToCall(transferTargetCall: WebRtcCall) {
|
||||||
|
val newCallId = CallIdGenerator.generate()
|
||||||
|
transferTargetCall.mxCall.transfer(
|
||||||
|
targetUserId = mxCall.opponentUserId,
|
||||||
|
targetRoomId = null,
|
||||||
|
createCallId = null,
|
||||||
|
awaitCallId = newCallId
|
||||||
|
)
|
||||||
|
mxCall.transfer(
|
||||||
|
targetUserId = transferTargetCall.mxCall.opponentUserId,
|
||||||
|
targetRoomId = null,
|
||||||
|
createCallId = newCallId,
|
||||||
|
awaitCallId = null
|
||||||
|
)
|
||||||
|
endCall(sendEndSignaling = false)
|
||||||
|
transferTargetCall.endCall(sendEndSignaling = false)
|
||||||
|
}
|
||||||
|
|
||||||
fun acceptIncomingCall() {
|
fun acceptIncomingCall() {
|
||||||
sessionScope?.launch {
|
sessionScope?.launch {
|
||||||
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
|
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
|
||||||
@ -729,7 +768,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
||||||
if (mxCall.state == CallState.Terminated) {
|
if (mxCall.state == CallState.Terminated) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -744,9 +783,9 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
mxCall.state = CallState.Terminated
|
mxCall.state = CallState.Terminated
|
||||||
sessionScope?.launch(dispatcher) {
|
sessionScope?.launch(dispatcher) {
|
||||||
release()
|
release()
|
||||||
|
onCallEnded(callId)
|
||||||
}
|
}
|
||||||
onCallEnded(callId)
|
if (sendEndSignaling) {
|
||||||
if (originatedByMe) {
|
|
||||||
if (wasRinging) {
|
if (wasRinging) {
|
||||||
mxCall.reject()
|
mxCall.reject()
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,6 +147,11 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
private val callsByCallId = ConcurrentHashMap<String, WebRtcCall>()
|
private val callsByCallId = ConcurrentHashMap<String, WebRtcCall>()
|
||||||
private val callsByRoomId = ConcurrentHashMap<String, MutableList<WebRtcCall>>()
|
private val callsByRoomId = ConcurrentHashMap<String, MutableList<WebRtcCall>>()
|
||||||
|
|
||||||
|
// Calls started as an attended transfer, ie. with the intention of transferring another
|
||||||
|
// call with a different party to this one.
|
||||||
|
// callId (target) -> call (transferee)
|
||||||
|
private val transferees = ConcurrentHashMap<String, WebRtcCall>()
|
||||||
|
|
||||||
fun getCallById(callId: String): WebRtcCall? {
|
fun getCallById(callId: String): WebRtcCall? {
|
||||||
return callsByCallId[callId]
|
return callsByCallId[callId]
|
||||||
}
|
}
|
||||||
@ -155,6 +160,10 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
return callsByRoomId[roomId] ?: emptyList()
|
return callsByRoomId[roomId] ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTransfereeForCallId(callId: String): WebRtcCall? {
|
||||||
|
return transferees[callId]
|
||||||
|
}
|
||||||
|
|
||||||
fun getCurrentCall(): WebRtcCall? {
|
fun getCurrentCall(): WebRtcCall? {
|
||||||
return currentCall.get()
|
return currentCall.get()
|
||||||
}
|
}
|
||||||
@ -229,34 +238,31 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
CallService.onCallTerminated(context, callId)
|
CallService.onCallTerminated(context, callId)
|
||||||
callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
|
callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
|
||||||
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
|
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
|
||||||
|
transferees.remove(callId)
|
||||||
if (getCurrentCall()?.callId == callId) {
|
if (getCurrentCall()?.callId == callId) {
|
||||||
val otherCall = getCalls().lastOrNull()
|
val otherCall = getCalls().lastOrNull()
|
||||||
currentCall.setAndNotify(otherCall)
|
currentCall.setAndNotify(otherCall)
|
||||||
}
|
}
|
||||||
// This must be done in this thread
|
// There is no active calls
|
||||||
executor.execute {
|
if (getCurrentCall() == null) {
|
||||||
// There is no active calls
|
Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
|
||||||
if (getCurrentCall() == null) {
|
peerConnectionFactory?.dispose()
|
||||||
Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
|
peerConnectionFactory = null
|
||||||
peerConnectionFactory?.dispose()
|
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
|
||||||
peerConnectionFactory = null
|
// did we start background sync? so we should stop it
|
||||||
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
|
if (isInBackground) {
|
||||||
// did we start background sync? so we should stop it
|
if (UPHelper.hasEndpoint(context)) {
|
||||||
if (isInBackground) {
|
currentSession?.stopAnyBackgroundSync()
|
||||||
if (UPHelper.hasEndpoint(context)) {
|
} else {
|
||||||
currentSession?.stopAnyBackgroundSync()
|
// for fdroid we should not stop, it should continue syncing
|
||||||
} else {
|
// maybe we should restore default timeout/delay though?
|
||||||
// for fdroid we should not stop, it should continue syncing
|
|
||||||
// maybe we should restore default timeout/delay though?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("## VOIP WebRtcPeerConnectionManager close() executor done")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean) {
|
suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) {
|
||||||
val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
|
val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
|
||||||
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
|
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
|
||||||
if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
|
if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
|
||||||
Timber.w("## VOIP you already have a call in this room")
|
Timber.w("## VOIP you already have a call in this room")
|
||||||
@ -274,7 +280,9 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
|
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
|
||||||
val webRtcCall = createWebRtcCall(mxCall, nativeRoomId)
|
val webRtcCall = createWebRtcCall(mxCall, nativeRoomId)
|
||||||
currentCall.setAndNotify(webRtcCall)
|
currentCall.setAndNotify(webRtcCall)
|
||||||
|
if (transferee != null) {
|
||||||
|
transferees[webRtcCall.callId] = transferee
|
||||||
|
}
|
||||||
CallService.onOutgoingCallRinging(
|
CallService.onOutgoingCallRinging(
|
||||||
context = context.applicationContext,
|
context = context.applicationContext,
|
||||||
callId = mxCall.callId)
|
callId = mxCall.callId)
|
||||||
|
@ -46,7 +46,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||||||
PLAIN("/plain", "<message>", R.string.command_description_plain, false),
|
PLAIN("/plain", "<message>", R.string.command_description_plain, false),
|
||||||
DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false),
|
DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false),
|
||||||
CONFETTI("/confetti", "<message>", R.string.command_confetti, false),
|
CONFETTI("/confetti", "<message>", R.string.command_confetti, false),
|
||||||
SNOW("/snow", "<message>", R.string.command_snow, false),
|
SNOWFALL("/snowfall", "<message>", R.string.command_snow, false),
|
||||||
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space, true),
|
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space, true),
|
||||||
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true),
|
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true),
|
||||||
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true),
|
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true),
|
||||||
|
@ -296,9 +296,9 @@ object CommandParser {
|
|||||||
val message = textMessage.substring(Command.CONFETTI.command.length).trim()
|
val message = textMessage.substring(Command.CONFETTI.command.length).trim()
|
||||||
ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message)
|
ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message)
|
||||||
}
|
}
|
||||||
Command.SNOW.command -> {
|
Command.SNOWFALL.command -> {
|
||||||
val message = textMessage.substring(Command.SNOW.command.length).trim()
|
val message = textMessage.substring(Command.SNOWFALL.command.length).trim()
|
||||||
ParsedCommand.SendChatEffect(ChatEffect.SNOW, message)
|
ParsedCommand.SendChatEffect(ChatEffect.SNOWFALL, message)
|
||||||
}
|
}
|
||||||
Command.CREATE_SPACE.command -> {
|
Command.CREATE_SPACE.command -> {
|
||||||
val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim()
|
val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim()
|
||||||
|
@ -33,6 +33,9 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var continueOnClick: ClickListener? = null
|
var continueOnClick: ClickListener? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var canContinue: Boolean = true
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var cancelOnClick: ClickListener? = null
|
var cancelOnClick: ClickListener? = null
|
||||||
|
|
||||||
@ -43,6 +46,7 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
|
|||||||
|
|
||||||
continueText?.let { holder.continueButton.text = it }
|
continueText?.let { holder.continueButton.text = it }
|
||||||
holder.continueButton.onClick(continueOnClick)
|
holder.continueButton.onClick(continueOnClick)
|
||||||
|
holder.continueButton.isEnabled = canContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.app.features.form
|
|||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
@ -52,7 +53,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
|||||||
var inputType: Int? = null
|
var inputType: Int? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var singleLine: Boolean? = null
|
var singleLine: Boolean = true
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var imeOptions: Int? = null
|
var imeOptions: Int? = null
|
||||||
@ -60,9 +61,13 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var endIconMode: Int? = null
|
var endIconMode: Int? = null
|
||||||
|
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
// FIXME restore EpoxyAttribute.Option.DoNotHash and fix that properly
|
||||||
|
@EpoxyAttribute
|
||||||
var onTextChange: ((String) -> Unit)? = null
|
var onTextChange: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var editorActionListener: TextView.OnEditorActionListener? = null
|
||||||
|
|
||||||
private val onTextChangeListener = object : SimpleTextWatcher() {
|
private val onTextChangeListener = object : SimpleTextWatcher() {
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
onTextChange?.invoke(s.toString())
|
onTextChange?.invoke(s.toString())
|
||||||
@ -80,10 +85,11 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
|||||||
|
|
||||||
holder.textInputEditText.isEnabled = enabled
|
holder.textInputEditText.isEnabled = enabled
|
||||||
inputType?.let { holder.textInputEditText.inputType = it }
|
inputType?.let { holder.textInputEditText.inputType = it }
|
||||||
holder.textInputEditText.isSingleLine = singleLine ?: false
|
holder.textInputEditText.isSingleLine = singleLine
|
||||||
holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE
|
holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE
|
||||||
|
|
||||||
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||||
|
holder.textInputEditText.setOnEditorActionListener(editorActionListener)
|
||||||
holder.bottomSeparator.isVisible = showBottomSeparator
|
holder.bottomSeparator.isVisible = showBottomSeparator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder<FormEditableS
|
|||||||
.into(holder.image)
|
.into(holder.image)
|
||||||
}
|
}
|
||||||
matrixItem != null -> {
|
matrixItem != null -> {
|
||||||
avatarRenderer?.renderSpace(matrixItem!!, holder.image)
|
avatarRenderer?.render(matrixItem!!, holder.image)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
avatarRenderer?.clear(holder.image)
|
avatarRenderer?.clear(holder.image)
|
||||||
|
@ -66,24 +66,24 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||||||
DrawableImageViewTarget(imageView))
|
DrawableImageViewTarget(imageView))
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
// fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
|
||||||
fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
// renderSpace(
|
||||||
val placeholder = getSpacePlaceholderDrawable(matrixItem)
|
// matrixItem,
|
||||||
val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
|
// imageView,
|
||||||
glideRequests
|
// GlideApp.with(imageView)
|
||||||
.load(resolvedUrl)
|
// )
|
||||||
.transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
|
// }
|
||||||
.placeholder(placeholder)
|
//
|
||||||
.into(DrawableImageViewTarget(imageView))
|
// @UiThread
|
||||||
}
|
// private fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
||||||
|
// val placeholder = getSpacePlaceholderDrawable(matrixItem)
|
||||||
fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
|
// val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
|
||||||
renderSpace(
|
// glideRequests
|
||||||
matrixItem,
|
// .load(resolvedUrl)
|
||||||
imageView,
|
// .transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
|
||||||
GlideApp.with(imageView)
|
// .placeholder(placeholder)
|
||||||
)
|
// .into(DrawableImageViewTarget(imageView))
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun clear(imageView: ImageView) {
|
fun clear(imageView: ImageView) {
|
||||||
// It can be called after recycler view is destroyed, just silently catch
|
// It can be called after recycler view is destroyed, just silently catch
|
||||||
@ -137,7 +137,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||||||
target: Target<Drawable>) {
|
target: Target<Drawable>) {
|
||||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||||
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
||||||
.apply(RequestOptions.circleCropTransform())
|
.apply {
|
||||||
|
when (matrixItem) {
|
||||||
|
is MatrixItem.SpaceItem -> {
|
||||||
|
transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
apply(RequestOptions.circleCropTransform())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.placeholder(placeholder)
|
.placeholder(placeholder)
|
||||||
.into(target)
|
.into(target)
|
||||||
}
|
}
|
||||||
@ -197,17 +206,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||||||
.beginConfig()
|
.beginConfig()
|
||||||
.bold()
|
.bold()
|
||||||
.endConfig()
|
.endConfig()
|
||||||
.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
.let {
|
||||||
}
|
when (matrixItem) {
|
||||||
|
is MatrixItem.SpaceItem -> {
|
||||||
@AnyThread
|
it.buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8))
|
||||||
fun getSpacePlaceholderDrawable(matrixItem: MatrixItem): Drawable {
|
}
|
||||||
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
|
else -> {
|
||||||
return TextDrawable.builder()
|
it.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
||||||
.beginConfig()
|
}
|
||||||
.bold()
|
}
|
||||||
.endConfig()
|
}
|
||||||
.buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRIVATE API *********************************************************************************
|
// PRIVATE API *********************************************************************************
|
||||||
|
@ -26,13 +26,13 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
enum class ChatEffect {
|
enum class ChatEffect {
|
||||||
CONFETTI,
|
CONFETTI,
|
||||||
SNOW
|
SNOWFALL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ChatEffect.toMessageType(): String {
|
fun ChatEffect.toMessageType(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
ChatEffect.CONFETTI -> MessageType.MSGTYPE_CONFETTI
|
ChatEffect.CONFETTI -> MessageType.MSGTYPE_CONFETTI
|
||||||
ChatEffect.SNOW -> MessageType.MSGTYPE_SNOW
|
ChatEffect.SNOWFALL -> MessageType.MSGTYPE_SNOWFALL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,14 +112,14 @@ class ChatEffectManager @Inject constructor() {
|
|||||||
private fun findEffect(content: MessageContent, event: TimelineEvent): ChatEffect? {
|
private fun findEffect(content: MessageContent, event: TimelineEvent): ChatEffect? {
|
||||||
return when (content.msgType) {
|
return when (content.msgType) {
|
||||||
MessageType.MSGTYPE_CONFETTI -> ChatEffect.CONFETTI
|
MessageType.MSGTYPE_CONFETTI -> ChatEffect.CONFETTI
|
||||||
MessageType.MSGTYPE_SNOW -> ChatEffect.SNOW
|
MessageType.MSGTYPE_SNOWFALL -> ChatEffect.SNOWFALL
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.MSGTYPE_TEXT -> {
|
MessageType.MSGTYPE_TEXT -> {
|
||||||
event.root.getClearContent().toModel<MessageContent>()?.body
|
event.root.getClearContent().toModel<MessageContent>()?.body
|
||||||
?.let { text ->
|
?.let { text ->
|
||||||
when {
|
when {
|
||||||
EMOJIS_FOR_CONFETTI.any { text.contains(it) } -> ChatEffect.CONFETTI
|
EMOJIS_FOR_CONFETTI.any { text.contains(it) } -> ChatEffect.CONFETTI
|
||||||
EMOJIS_FOR_SNOW.any { text.contains(it) } -> ChatEffect.SNOW
|
EMOJIS_FOR_SNOWFALL.any { text.contains(it) } -> ChatEffect.SNOWFALL
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ class ChatEffectManager @Inject constructor() {
|
|||||||
"🎉",
|
"🎉",
|
||||||
"🎊"
|
"🎊"
|
||||||
)
|
)
|
||||||
private val EMOJIS_FOR_SNOW = listOf(
|
private val EMOJIS_FOR_SNOWFALL = listOf(
|
||||||
"⛄️",
|
"⛄️",
|
||||||
"☃️",
|
"☃️",
|
||||||
"❄️"
|
"❄️"
|
||||||
|
@ -438,7 +438,7 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
.setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f)
|
.setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f)
|
||||||
.streamFor(150, 3000L)
|
.streamFor(150, 3000L)
|
||||||
}
|
}
|
||||||
ChatEffect.SNOW -> {
|
ChatEffect.SNOWFALL -> {
|
||||||
views.viewSnowFall.isVisible = true
|
views.viewSnowFall.isVisible = true
|
||||||
views.viewSnowFall.restartFalling()
|
views.viewSnowFall.restartFalling()
|
||||||
}
|
}
|
||||||
|
@ -893,7 +893,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
if (sendChatEffect.message.isBlank()) {
|
if (sendChatEffect.message.isBlank()) {
|
||||||
val defaultMessage = stringProvider.getString(when (sendChatEffect.chatEffect) {
|
val defaultMessage = stringProvider.getString(when (sendChatEffect.chatEffect) {
|
||||||
ChatEffect.CONFETTI -> R.string.default_message_emote_confetti
|
ChatEffect.CONFETTI -> R.string.default_message_emote_confetti
|
||||||
ChatEffect.SNOW -> R.string.default_message_emote_snow
|
ChatEffect.SNOWFALL -> R.string.default_message_emote_snow
|
||||||
})
|
})
|
||||||
room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
|
room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,4 +30,5 @@ sealed class RoomListAction : VectorViewModelAction {
|
|||||||
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
|
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
|
||||||
data class LeaveRoom(val roomId: String) : RoomListAction()
|
data class LeaveRoom(val roomId: String) : RoomListAction()
|
||||||
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
||||||
|
data class ShowRoomDetails(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
||||||
}
|
}
|
||||||
|
@ -108,10 +108,11 @@ class RoomListFragment @Inject constructor(
|
|||||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||||
roomListViewModel.observeViewEvents {
|
roomListViewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is RoomListViewEvents.Loading -> showLoading(it.message)
|
is RoomListViewEvents.Loading -> showLoading(it.message)
|
||||||
is RoomListViewEvents.Failure -> showFailure(it.throwable)
|
is RoomListViewEvents.Failure -> showFailure(it.throwable)
|
||||||
is RoomListViewEvents.SelectRoom -> handleSelectRoom(it)
|
is RoomListViewEvents.SelectRoom -> handleSelectRoom(it)
|
||||||
is RoomListViewEvents.Done -> Unit
|
is RoomListViewEvents.Done -> Unit
|
||||||
|
is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +156,10 @@ class RoomListFragment @Inject constructor(
|
|||||||
showErrorInSnackbar(throwable)
|
showErrorInSnackbar(throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleShowMxToLink(link: String) {
|
||||||
|
navigator.openMatrixToBottomSheet(requireContext(), link)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
adapterInfosList.onEach { it.contentEpoxyController.removeModelBuildListener(modelBuildListener) }
|
adapterInfosList.onEach { it.contentEpoxyController.removeModelBuildListener(modelBuildListener) }
|
||||||
adapterInfosList.clear()
|
adapterInfosList.clear()
|
||||||
@ -474,6 +479,10 @@ class RoomListFragment @Inject constructor(
|
|||||||
roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers))
|
roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSuggestedRoomClicked(room: SpaceChildInfo) {
|
||||||
|
roomListViewModel.handle(RoomListAction.ShowRoomDetails(room.childRoomId, room.viaServers))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRejectRoomInvitation(room: RoomSummary) {
|
override fun onRejectRoomInvitation(room: RoomSummary) {
|
||||||
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
|
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
|
||||||
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
|
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
|
||||||
|
@ -26,4 +26,5 @@ interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListen
|
|||||||
fun onRejectRoomInvitation(room: RoomSummary)
|
fun onRejectRoomInvitation(room: RoomSummary)
|
||||||
fun onAcceptRoomInvitation(room: RoomSummary)
|
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||||
fun onJoinSuggestedRoom(room: SpaceChildInfo)
|
fun onJoinSuggestedRoom(room: SpaceChildInfo)
|
||||||
|
fun onSuggestedRoomClicked(room: SpaceChildInfo)
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,5 @@ sealed class RoomListViewEvents : VectorViewEvents {
|
|||||||
|
|
||||||
data class SelectRoom(val roomSummary: RoomSummary) : RoomListViewEvents()
|
data class SelectRoom(val roomSummary: RoomSummary) : RoomListViewEvents()
|
||||||
object Done : RoomListViewEvents()
|
object Done : RoomListViewEvents()
|
||||||
|
data class NavigateToMxToBottomSheet(val link: String) : RoomListViewEvents()
|
||||||
}
|
}
|
||||||
|
@ -161,6 +161,7 @@ class RoomListViewModel @Inject constructor(
|
|||||||
is RoomListAction.ToggleTag -> handleToggleTag(action)
|
is RoomListAction.ToggleTag -> handleToggleTag(action)
|
||||||
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
|
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
|
||||||
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
|
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
|
||||||
|
is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +290,12 @@ class RoomListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleShowRoomDetails(action: RoomListAction.ShowRoomDetails) {
|
||||||
|
session.permalinkService().createRoomPermalink(action.roomId, action.viaServers)?.let {
|
||||||
|
_viewEvents.post(RoomListViewEvents.NavigateToMxToBottomSheet(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleToggleTag(action: RoomListAction.ToggleTag) {
|
private fun handleToggleTag(action: RoomListAction.ToggleTag) {
|
||||||
session.getRoom(action.roomId)?.let { room ->
|
session.getRoom(action.roomId)?.let { room ->
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
@ -56,7 +55,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||||||
|
|
||||||
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
|
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
|
||||||
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
|
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
|
||||||
onJoinClick: View.OnClickListener): VectorEpoxyModel<*> {
|
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||||
return SpaceChildInfoItem_()
|
return SpaceChildInfoItem_()
|
||||||
.id("sug_${spaceChildInfo.childRoomId}")
|
.id("sug_${spaceChildInfo.childRoomId}")
|
||||||
.matrixItem(spaceChildInfo.toMatrixItem())
|
.matrixItem(spaceChildInfo.toMatrixItem())
|
||||||
@ -65,7 +64,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||||||
.buttonLabel(stringProvider.getString(R.string.join))
|
.buttonLabel(stringProvider.getString(R.string.join))
|
||||||
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
|
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
|
||||||
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
|
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
|
||||||
.buttonClickListener(onJoinClick)
|
.buttonClickListener(DebouncedClickListener({ listener?.onJoinSuggestedRoom(spaceChildInfo) }))
|
||||||
|
.itemClickListener(DebouncedClickListener({ listener?.onSuggestedRoomClicked(spaceChildInfo) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||||
|
@ -48,7 +48,6 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
|||||||
|
|
||||||
@EpoxyAttribute var memberCount: Int = 0
|
@EpoxyAttribute var memberCount: Int = 0
|
||||||
@EpoxyAttribute var loading: Boolean = false
|
@EpoxyAttribute var loading: Boolean = false
|
||||||
@EpoxyAttribute var space: Boolean = false
|
|
||||||
|
|
||||||
@EpoxyAttribute var buttonLabel: String? = null
|
@EpoxyAttribute var buttonLabel: String? = null
|
||||||
|
|
||||||
@ -63,12 +62,8 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
|||||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
itemLongClickListener?.onLongClick(it) ?: false
|
itemLongClickListener?.onLongClick(it) ?: false
|
||||||
}
|
}
|
||||||
holder.titleView.text = matrixItem.getBestName()
|
holder.titleView.text = matrixItem.displayName ?: holder.rootView.context.getString(R.string.unnamed_room)
|
||||||
if (space) {
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
|
||||||
} else {
|
|
||||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.descriptionText.text = span {
|
holder.descriptionText.text = span {
|
||||||
span {
|
span {
|
||||||
|
@ -24,11 +24,7 @@ class SuggestedRoomListController(
|
|||||||
|
|
||||||
override fun buildModels(data: SuggestedRoomInfo?) {
|
override fun buildModels(data: SuggestedRoomInfo?) {
|
||||||
data?.rooms?.forEach { info ->
|
data?.rooms?.forEach { info ->
|
||||||
roomSummaryItemFactory.createSuggestion(info, data.joinEcho) {
|
add(roomSummaryItemFactory.createSuggestion(info, data.joinEcho, listener))
|
||||||
listener?.onJoinSuggestedRoom(info)
|
|
||||||
}.let {
|
|
||||||
add(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,14 +41,15 @@ data class MatrixToBottomSheetState(
|
|||||||
|
|
||||||
sealed class RoomInfoResult {
|
sealed class RoomInfoResult {
|
||||||
data class FullInfo(
|
data class FullInfo(
|
||||||
val roomItem: MatrixItem.RoomItem,
|
val roomItem: MatrixItem,
|
||||||
val name: String,
|
val name: String,
|
||||||
val topic: String,
|
val topic: String,
|
||||||
val memberCount: Int?,
|
val memberCount: Int?,
|
||||||
val alias: String?,
|
val alias: String?,
|
||||||
val membership: Membership,
|
val membership: Membership,
|
||||||
val roomType: String?,
|
val roomType: String?,
|
||||||
val viaServers: List<String>?
|
val viaServers: List<String>?,
|
||||||
|
val isPublic: Boolean
|
||||||
) : RoomInfoResult()
|
) : RoomInfoResult()
|
||||||
|
|
||||||
data class PartialInfo(
|
data class PartialInfo(
|
||||||
|
@ -118,11 +118,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
|
|||||||
session.getRoom(permalinkData.roomIdOrAlias)
|
session.getRoom(permalinkData.roomIdOrAlias)
|
||||||
}
|
}
|
||||||
?.roomSummary()
|
?.roomSummary()
|
||||||
// don't take if not active, as it could be outdated
|
// don't take if not Join, as it could be outdated
|
||||||
?.takeIf { it.membership.isActive() }
|
?.takeIf { it.membership == Membership.JOIN }
|
||||||
// XXX fix that
|
if (knownRoom != null) {
|
||||||
val forceRefresh = true
|
|
||||||
if (!forceRefresh && knownRoom != null) {
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
roomPeekResult = Success(
|
roomPeekResult = Success(
|
||||||
@ -134,7 +132,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
|
|||||||
alias = knownRoom.canonicalAlias,
|
alias = knownRoom.canonicalAlias,
|
||||||
membership = knownRoom.membership,
|
membership = knownRoom.membership,
|
||||||
roomType = knownRoom.roomType,
|
roomType = knownRoom.roomType,
|
||||||
viaServers = null
|
viaServers = null,
|
||||||
|
isPublic = knownRoom.isPublic
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -150,7 +149,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
|
|||||||
alias = peekResult.alias,
|
alias = peekResult.alias,
|
||||||
membership = knownRoom?.membership ?: Membership.NONE,
|
membership = knownRoom?.membership ?: Membership.NONE,
|
||||||
roomType = peekResult.roomType,
|
roomType = peekResult.roomType,
|
||||||
viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters
|
viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters,
|
||||||
|
isPublic = peekResult.isPublic
|
||||||
).also {
|
).also {
|
||||||
peekResult.someMembers?.let { checkForKnownMembers(it) }
|
peekResult.someMembers?.let { checkForKnownMembers(it) }
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MatrixToRoomSpaceFragment @Inject constructor(
|
class MatrixToRoomSpaceFragment @Inject constructor(
|
||||||
private val avatarRenderer: AvatarRenderer
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val spaceCardRenderer: SpaceCardRenderer
|
||||||
) : VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() {
|
) : VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() {
|
||||||
|
|
||||||
private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel()
|
private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel()
|
||||||
@ -78,12 +79,19 @@ class MatrixToRoomSpaceFragment @Inject constructor(
|
|||||||
when (val peek = item.invoke()) {
|
when (val peek = item.invoke()) {
|
||||||
is RoomInfoResult.FullInfo -> {
|
is RoomInfoResult.FullInfo -> {
|
||||||
val matrixItem = peek.roomItem
|
val matrixItem = peek.roomItem
|
||||||
|
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
|
||||||
if (peek.roomType == RoomType.SPACE) {
|
if (peek.roomType == RoomType.SPACE) {
|
||||||
views.matrixToBetaTag.isVisible = true
|
views.matrixToBetaTag.isVisible = true
|
||||||
avatarRenderer.renderSpace(matrixItem, views.matrixToCardAvatar)
|
views.matrixToAccessImage.isVisible = true
|
||||||
|
if (peek.isPublic) {
|
||||||
|
views.matrixToAccessText.setTextOrHide(context?.getString(R.string.public_space))
|
||||||
|
views.matrixToAccessImage.setImageResource(R.drawable.ic_public_room)
|
||||||
|
} else {
|
||||||
|
views.matrixToAccessText.setTextOrHide(context?.getString(R.string.private_space))
|
||||||
|
views.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
views.matrixToBetaTag.isVisible = false
|
views.matrixToBetaTag.isVisible = false
|
||||||
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
|
|
||||||
}
|
}
|
||||||
views.matrixToCardNameText.setTextOrHide(peek.name)
|
views.matrixToCardNameText.setTextOrHide(peek.name)
|
||||||
views.matrixToCardAliasText.setTextOrHide(peek.alias)
|
views.matrixToCardAliasText.setTextOrHide(peek.alias)
|
||||||
@ -166,25 +174,12 @@ class MatrixToRoomSpaceFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val images = listOf(views.knownMember1, views.knownMember2, views.knownMember3, views.knownMember4, views.knownMember5)
|
listOf(views.knownMember1, views.knownMember2, views.knownMember3, views.knownMember4, views.knownMember5)
|
||||||
.onEach { it.isGone = true }
|
.onEach { it.isGone = true }
|
||||||
when (state.peopleYouKnow) {
|
when (state.peopleYouKnow) {
|
||||||
is Success -> {
|
is Success -> {
|
||||||
val someYouKnow = state.peopleYouKnow.invoke()
|
val someYouKnow = state.peopleYouKnow.invoke()
|
||||||
if (someYouKnow.isEmpty()) {
|
spaceCardRenderer.renderPeopleYouKnow(views, someYouKnow)
|
||||||
views.peopleYouMayKnowText.isVisible = false
|
|
||||||
} else {
|
|
||||||
someYouKnow.forEachIndexed { index, item ->
|
|
||||||
images[index].isVisible = true
|
|
||||||
avatarRenderer.render(item, images[index])
|
|
||||||
}
|
|
||||||
views.peopleYouMayKnowText.setTextOrHide(
|
|
||||||
resources.getQuantityString(R.plurals.space_people_you_know,
|
|
||||||
someYouKnow.count(),
|
|
||||||
someYouKnow.count()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
views.peopleYouMayKnowText.isVisible = false
|
views.peopleYouMayKnowText.isVisible = false
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.matrixto
|
||||||
|
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.databinding.FragmentMatrixToRoomSpaceCardBinding
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
|
import org.matrix.android.sdk.api.session.user.model.User
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SpaceCardRenderer @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun render(spaceSummary: RoomSummary?,
|
||||||
|
peopleYouKnow: List<User>,
|
||||||
|
matrixLinkCallback: TimelineEventController.UrlClickCallback?,
|
||||||
|
inCard: FragmentMatrixToRoomSpaceCardBinding) {
|
||||||
|
if (spaceSummary == null) {
|
||||||
|
inCard.matrixToCardContentVisibility.isVisible = false
|
||||||
|
inCard.matrixToCardButtonLoading.isVisible = true
|
||||||
|
} else {
|
||||||
|
inCard.matrixToCardContentVisibility.isVisible = true
|
||||||
|
inCard.matrixToCardButtonLoading.isVisible = false
|
||||||
|
avatarRenderer.render(spaceSummary.toMatrixItem(), inCard.matrixToCardAvatar)
|
||||||
|
inCard.matrixToCardNameText.text = spaceSummary.name
|
||||||
|
inCard.matrixToBetaTag.isVisible = true
|
||||||
|
inCard.matrixToCardAliasText.setTextOrHide(spaceSummary.canonicalAlias)
|
||||||
|
inCard.matrixToCardDescText.setTextOrHide(spaceSummary.topic.linkify(matrixLinkCallback))
|
||||||
|
if (spaceSummary.isPublic) {
|
||||||
|
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.public_space))
|
||||||
|
inCard.matrixToAccessImage.isVisible = true
|
||||||
|
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_public_room)
|
||||||
|
} else {
|
||||||
|
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.private_space))
|
||||||
|
inCard.matrixToAccessImage.isVisible = true
|
||||||
|
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
|
||||||
|
}
|
||||||
|
val memberCount = spaceSummary.otherMemberIds.size
|
||||||
|
if (memberCount != 0) {
|
||||||
|
inCard.matrixToMemberPills.isVisible = true
|
||||||
|
inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
|
||||||
|
} else {
|
||||||
|
// hide the pill
|
||||||
|
inCard.matrixToMemberPills.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() })
|
||||||
|
}
|
||||||
|
inCard.matrixToCardDescText.movementMethod = createLinkMovementMethod(object : TimelineEventController.UrlClickCallback {
|
||||||
|
override fun onUrlClicked(url: String, title: String): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUrlLongClicked(url: String): Boolean {
|
||||||
|
// host.callback?.onUrlInTopicLongClicked(url)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(spaceChildInfo: SpaceChildInfo?,
|
||||||
|
peopleYouKnow: List<User>,
|
||||||
|
matrixLinkCallback: TimelineEventController.UrlClickCallback?,
|
||||||
|
inCard: FragmentMatrixToRoomSpaceCardBinding) {
|
||||||
|
if (spaceChildInfo == null) {
|
||||||
|
inCard.matrixToCardContentVisibility.isVisible = false
|
||||||
|
inCard.matrixToCardButtonLoading.isVisible = true
|
||||||
|
} else {
|
||||||
|
inCard.matrixToCardContentVisibility.isVisible = true
|
||||||
|
inCard.matrixToCardButtonLoading.isVisible = false
|
||||||
|
avatarRenderer.render(spaceChildInfo.toMatrixItem(), inCard.matrixToCardAvatar)
|
||||||
|
inCard.matrixToCardNameText.setTextOrHide(spaceChildInfo.name)
|
||||||
|
inCard.matrixToBetaTag.isVisible = true
|
||||||
|
inCard.matrixToCardAliasText.setTextOrHide(spaceChildInfo.canonicalAlias)
|
||||||
|
inCard.matrixToCardDescText.setTextOrHide(spaceChildInfo.topic?.linkify(matrixLinkCallback))
|
||||||
|
if (spaceChildInfo.worldReadable) {
|
||||||
|
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.public_space))
|
||||||
|
inCard.matrixToAccessImage.isVisible = true
|
||||||
|
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_public_room)
|
||||||
|
} else {
|
||||||
|
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.private_space))
|
||||||
|
inCard.matrixToAccessImage.isVisible = true
|
||||||
|
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
|
||||||
|
}
|
||||||
|
val memberCount = spaceChildInfo.activeMemberCount ?: 0
|
||||||
|
if (memberCount != 0) {
|
||||||
|
inCard.matrixToMemberPills.isVisible = true
|
||||||
|
inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
|
||||||
|
} else {
|
||||||
|
// hide the pill
|
||||||
|
inCard.matrixToMemberPills.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renderPeopleYouKnow(inCard: FragmentMatrixToRoomSpaceCardBinding, peopleYouKnow: List<MatrixItem.UserItem>) {
|
||||||
|
val images = listOf(
|
||||||
|
inCard.knownMember1,
|
||||||
|
inCard.knownMember2,
|
||||||
|
inCard.knownMember3,
|
||||||
|
inCard.knownMember4,
|
||||||
|
inCard.knownMember5
|
||||||
|
).onEach { it.isGone = true }
|
||||||
|
|
||||||
|
if (peopleYouKnow.isEmpty()) {
|
||||||
|
inCard.peopleYouMayKnowText.isVisible = false
|
||||||
|
} else {
|
||||||
|
peopleYouKnow.forEachIndexed { index, item ->
|
||||||
|
images[index].isVisible = true
|
||||||
|
avatarRenderer.render(item, images[index])
|
||||||
|
}
|
||||||
|
inCard.peopleYouMayKnowText.setTextOrHide(
|
||||||
|
stringProvider.getQuantityString(R.plurals.space_people_you_know,
|
||||||
|
peopleYouKnow.count(),
|
||||||
|
peopleYouKnow.count()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -245,7 +245,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
|||||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||||
val resolvedUrl = when (mode) {
|
val resolvedUrl = when (mode) {
|
||||||
Mode.FULL_SIZE,
|
Mode.FULL_SIZE,
|
||||||
Mode.STICKER -> resolveUrl(data)
|
Mode.STICKER -> resolveUrl(data)
|
||||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
}
|
}
|
||||||
// Fallback to base url
|
// Fallback to base url
|
||||||
@ -313,7 +313,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
|||||||
finalHeight = min(maxImageWidth * height / width, maxImageHeight)
|
finalHeight = min(maxImageWidth * height / width, maxImageHeight)
|
||||||
finalWidth = finalHeight * width / height
|
finalWidth = finalHeight * width / height
|
||||||
}
|
}
|
||||||
Mode.STICKER -> {
|
Mode.STICKER -> {
|
||||||
// limit on width
|
// limit on width
|
||||||
val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2)
|
val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2)
|
||||||
finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp)
|
finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp)
|
||||||
|
@ -65,6 +65,7 @@ import im.vector.app.features.pin.PinActivity
|
|||||||
import im.vector.app.features.pin.PinArgs
|
import im.vector.app.features.pin.PinArgs
|
||||||
import im.vector.app.features.pin.PinMode
|
import im.vector.app.features.pin.PinMode
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity
|
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity
|
||||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||||
@ -86,7 +87,6 @@ import im.vector.app.features.widgets.WidgetArgsBuilder
|
|||||||
import im.vector.app.space
|
import im.vector.app.space
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||||
@ -129,7 +129,7 @@ class DefaultNavigator @Inject constructor(
|
|||||||
}
|
}
|
||||||
appStateHandler.setCurrentSpace(spaceId)
|
appStateHandler.setCurrentSpace(spaceId)
|
||||||
when (postSwitchSpaceAction) {
|
when (postSwitchSpaceAction) {
|
||||||
Navigator.PostSwitchSpaceAction.None -> {
|
Navigator.PostSwitchSpaceAction.None -> {
|
||||||
// go back to home if we are showing room details?
|
// go back to home if we are showing room details?
|
||||||
// This is a bit ugly, but the navigator is supposed to know about the activity stack
|
// This is a bit ugly, but the navigator is supposed to know about the activity stack
|
||||||
if (context is RoomDetailActivity) {
|
if (context is RoomDetailActivity) {
|
||||||
@ -139,7 +139,7 @@ class DefaultNavigator @Inject constructor(
|
|||||||
Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> {
|
Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> {
|
||||||
startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false)
|
startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false)
|
||||||
}
|
}
|
||||||
is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> {
|
is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> {
|
||||||
val args = RoomDetailArgs(
|
val args = RoomDetailArgs(
|
||||||
postSwitchSpaceAction.roomId,
|
postSwitchSpaceAction.roomId,
|
||||||
eventId = null,
|
eventId = null,
|
||||||
@ -278,7 +278,7 @@ class DefaultNavigator @Inject constructor(
|
|||||||
val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
|
val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
is RoomGroupingMethod.BySpace -> {
|
is RoomGroupingMethod.BySpace -> {
|
||||||
val selectedSpace = groupingMethod.space()
|
val selectedSpace = groupingMethod.space()
|
||||||
if (selectedSpace == null) {
|
if (selectedSpace == null) {
|
||||||
val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
|
val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
|
||||||
@ -320,7 +320,7 @@ class DefaultNavigator @Inject constructor(
|
|||||||
val intent = InviteUsersToRoomActivity.getIntent(context, roomId)
|
val intent = InviteUsersToRoomActivity.getIntent(context, roomId)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
is RoomGroupingMethod.BySpace -> {
|
is RoomGroupingMethod.BySpace -> {
|
||||||
if (currentGroupingMethod.spaceSummary != null) {
|
if (currentGroupingMethod.spaceSummary != null) {
|
||||||
// let user decides if he does it from space or room
|
// let user decides if he does it from space or room
|
||||||
(context as? AppCompatActivity)?.supportFragmentManager?.let { fm ->
|
(context as? AppCompatActivity)?.supportFragmentManager?.let { fm ->
|
||||||
|
@ -26,11 +26,11 @@ import im.vector.app.features.crypto.recover.SetupMode
|
|||||||
import im.vector.app.features.login.LoginConfig
|
import im.vector.app.features.login.LoginConfig
|
||||||
import im.vector.app.features.media.AttachmentData
|
import im.vector.app.features.media.AttachmentData
|
||||||
import im.vector.app.features.pin.PinMode
|
import im.vector.app.features.pin.PinMode
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import im.vector.app.features.share.SharedData
|
import im.vector.app.features.share.SharedData
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
@ -44,7 +44,7 @@ interface Navigator {
|
|||||||
sealed class PostSwitchSpaceAction {
|
sealed class PostSwitchSpaceAction {
|
||||||
object None : PostSwitchSpaceAction()
|
object None : PostSwitchSpaceAction()
|
||||||
data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction()
|
data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction()
|
||||||
object OpenAddExistingRooms: PostSwitchSpaceAction()
|
object OpenAddExistingRooms : PostSwitchSpaceAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: PostSwitchSpaceAction)
|
fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: PostSwitchSpaceAction)
|
||||||
|
@ -21,7 +21,6 @@ import com.airbnb.mvrx.MvRxState
|
|||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
|
|
||||||
data class PublicRoomsViewState(
|
data class PublicRoomsViewState(
|
||||||
// The current filter
|
// The current filter
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.app.features.roomdirectory
|
package im.vector.app.features.roomdirectory
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
|
|
||||||
sealed class RoomDirectoryAction : VectorViewModelAction {
|
sealed class RoomDirectoryAction : VectorViewModelAction {
|
||||||
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction()
|
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction()
|
||||||
|
@ -25,6 +25,7 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.di.ScreenComponent
|
import im.vector.app.core.di.ScreenComponent
|
||||||
import im.vector.app.core.extensions.addFragment
|
import im.vector.app.core.extensions.addFragment
|
||||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||||
|
import im.vector.app.core.extensions.popBackstack
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivitySimpleBinding
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
|
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
|
||||||
@ -58,7 +59,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
|||||||
.observe()
|
.observe()
|
||||||
.subscribe { sharedAction ->
|
.subscribe { sharedAction ->
|
||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
is RoomDirectorySharedAction.Back -> onBackPressed()
|
is RoomDirectorySharedAction.Back -> popBackstack()
|
||||||
is RoomDirectorySharedAction.CreateRoom -> {
|
is RoomDirectorySharedAction.CreateRoom -> {
|
||||||
// Transmit the filter to the CreateRoomFragment
|
// Transmit the filter to the CreateRoomFragment
|
||||||
withState(roomDirectoryViewModel) {
|
withState(roomDirectoryViewModel) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,13 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.model.thirdparty
|
package im.vector.app.features.roomdirectory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class describes a rooms directory server.
|
* This class describes a rooms directory server protocol.
|
||||||
*/
|
*/
|
||||||
data class RoomDirectoryData(
|
data class RoomDirectoryData(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server name (might be null)
|
* The server name (might be null)
|
||||||
* Set null when the server is the current user's home server.
|
* Set null when the server is the current user's home server.
|
||||||
@ -30,7 +29,12 @@ data class RoomDirectoryData(
|
|||||||
/**
|
/**
|
||||||
* The display name (the server description)
|
* The display name (the server description)
|
||||||
*/
|
*/
|
||||||
val displayName: String = DEFAULT_HOME_SERVER_NAME,
|
val displayName: String = MATRIX_PROTOCOL_NAME,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the avatar url
|
||||||
|
*/
|
||||||
|
val avatarUrl: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The third party server identifier
|
* The third party server identifier
|
||||||
@ -40,15 +44,10 @@ data class RoomDirectoryData(
|
|||||||
/**
|
/**
|
||||||
* Tell if all the federated servers must be included
|
* Tell if all the federated servers must be included
|
||||||
*/
|
*/
|
||||||
val includeAllNetworks: Boolean = false,
|
val includeAllNetworks: Boolean = false
|
||||||
|
|
||||||
/**
|
|
||||||
* the avatar url
|
|
||||||
*/
|
|
||||||
val avatarUrl: String? = null
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_HOME_SERVER_NAME = "Matrix"
|
const val MATRIX_PROTOCOL_NAME = "Matrix"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomdirectory
|
||||||
|
|
||||||
|
data class RoomDirectoryServer(
|
||||||
|
val serverName: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if this is the current user server
|
||||||
|
*/
|
||||||
|
val isUserServer: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if manually added, so it can be removed by the user
|
||||||
|
*/
|
||||||
|
val isManuallyAdded: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported protocols
|
||||||
|
* TODO Rename RoomDirectoryData to RoomDirectoryProtocols
|
||||||
|
*/
|
||||||
|
val protocols: List<RoomDirectoryData>
|
||||||
|
)
|
@ -37,7 +37,6 @@ 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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -230,9 +229,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
|
|||||||
Timber.w("Try to join an already joining room. Should not happen")
|
Timber.w("Try to join an already joining room. Should not happen")
|
||||||
return@withState
|
return@withState
|
||||||
}
|
}
|
||||||
val viaServers = state.roomDirectoryData.homeServer
|
val viaServers = listOfNotNull(state.roomDirectoryData.homeServer)
|
||||||
?.let { listOf(it) }
|
|
||||||
.orEmpty()
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
session.joinRoom(action.roomId, viaServers = viaServers)
|
session.joinRoom(action.roomId, viaServers = viaServers)
|
||||||
|
@ -75,6 +75,7 @@ class CreateRoomController @Inject constructor(
|
|||||||
id("topic")
|
id("topic")
|
||||||
enabled(enableFormElement)
|
enabled(enableFormElement)
|
||||||
value(viewState.roomTopic)
|
value(viewState.roomTopic)
|
||||||
|
singleLine(false)
|
||||||
hint(host.stringProvider.getString(R.string.create_room_topic_hint))
|
hint(host.stringProvider.getString(R.string.create_room_topic_hint))
|
||||||
|
|
||||||
onTextChange { text ->
|
onTextChange { text ->
|
||||||
|
@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomdirectory.picker
|
package im.vector.app.features.roomdirectory.picker
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
@ -43,6 +45,9 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var includeAllNetworks: Boolean = false
|
var includeAllNetworks: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var checked: Boolean = false
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var globalListener: (() -> Unit)? = null
|
var globalListener: (() -> Unit)? = null
|
||||||
|
|
||||||
@ -63,6 +68,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
|
|||||||
|
|
||||||
holder.nameView.text = directoryName
|
holder.nameView.text = directoryName
|
||||||
holder.descriptionView.setTextOrHide(directoryDescription)
|
holder.descriptionView.setTextOrHide(directoryDescription)
|
||||||
|
holder.checkedView.isVisible = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
@ -71,5 +77,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()
|
|||||||
val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
|
val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
|
||||||
val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
|
val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
|
||||||
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
|
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
|
||||||
|
val checkedView by bind<View>(R.id.itemRoomDirectoryChecked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,55 +18,110 @@ package im.vector.app.features.roomdirectory.picker
|
|||||||
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringArrayProvider
|
import im.vector.app.core.resources.StringArrayProvider
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryServer
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
|
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomDirectoryListCreator @Inject constructor(private val stringArrayProvider: StringArrayProvider,
|
class RoomDirectoryListCreator @Inject constructor(
|
||||||
private val session: Session) {
|
private val stringArrayProvider: StringArrayProvider,
|
||||||
|
private val session: Session
|
||||||
|
) {
|
||||||
|
|
||||||
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryData> {
|
fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>,
|
||||||
val result = ArrayList<RoomDirectoryData>()
|
customHomeservers: Set<String>): List<RoomDirectoryServer> {
|
||||||
|
val result = ArrayList<RoomDirectoryServer>()
|
||||||
|
|
||||||
|
val protocols = ArrayList<RoomDirectoryData>()
|
||||||
|
|
||||||
// Add user homeserver name
|
// Add user homeserver name
|
||||||
val userHsName = session.myUserId.substringAfter(":")
|
val userHsName = session.myUserId.substringAfter(":")
|
||||||
|
|
||||||
result.add(RoomDirectoryData(
|
// Add default protocol
|
||||||
displayName = userHsName,
|
protocols.add(
|
||||||
includeAllNetworks = true
|
RoomDirectoryData(
|
||||||
))
|
homeServer = null,
|
||||||
|
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
|
||||||
// Add user's HS but for Matrix public rooms only
|
includeAllNetworks = false
|
||||||
result.add(RoomDirectoryData())
|
)
|
||||||
|
)
|
||||||
// Add custom directory servers
|
|
||||||
val hsNamesList = stringArrayProvider.getStringArray(R.array.room_directory_servers)
|
|
||||||
hsNamesList.forEach {
|
|
||||||
if (it != userHsName) {
|
|
||||||
// Use the server name as a default display name
|
|
||||||
result.add(RoomDirectoryData(
|
|
||||||
homeServer = it,
|
|
||||||
displayName = it,
|
|
||||||
includeAllNetworks = true
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add result of the request
|
// Add result of the request
|
||||||
thirdPartyProtocolData.forEach {
|
thirdPartyProtocolData.forEach {
|
||||||
it.value.instances?.forEach { thirdPartyProtocolInstance ->
|
it.value.instances?.forEach { thirdPartyProtocolInstance ->
|
||||||
result.add(RoomDirectoryData(
|
protocols.add(
|
||||||
homeServer = null,
|
RoomDirectoryData(
|
||||||
displayName = thirdPartyProtocolInstance.desc ?: "",
|
homeServer = null,
|
||||||
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
|
displayName = thirdPartyProtocolInstance.desc ?: "",
|
||||||
includeAllNetworks = false,
|
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
|
||||||
// Default to protocol icon
|
includeAllNetworks = false,
|
||||||
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon
|
// Default to protocol icon
|
||||||
))
|
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add all rooms
|
||||||
|
protocols.add(
|
||||||
|
RoomDirectoryData(
|
||||||
|
homeServer = null,
|
||||||
|
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
|
||||||
|
includeAllNetworks = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.add(
|
||||||
|
RoomDirectoryServer(
|
||||||
|
serverName = userHsName,
|
||||||
|
isUserServer = true,
|
||||||
|
isManuallyAdded = false,
|
||||||
|
protocols = protocols
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add custom directory servers, form the config file, excluding the current user homeserver
|
||||||
|
stringArrayProvider.getStringArray(R.array.room_directory_servers)
|
||||||
|
.filter { it != userHsName }
|
||||||
|
.forEach {
|
||||||
|
// Use the server name as a default display name
|
||||||
|
result.add(
|
||||||
|
RoomDirectoryServer(
|
||||||
|
serverName = it,
|
||||||
|
isUserServer = false,
|
||||||
|
isManuallyAdded = false,
|
||||||
|
protocols = listOf(
|
||||||
|
RoomDirectoryData(
|
||||||
|
homeServer = it,
|
||||||
|
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
|
||||||
|
includeAllNetworks = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add manually added server by the user
|
||||||
|
customHomeservers
|
||||||
|
.forEach {
|
||||||
|
// Use the server name as a default display name
|
||||||
|
result.add(
|
||||||
|
RoomDirectoryServer(
|
||||||
|
serverName = it,
|
||||||
|
isUserServer = false,
|
||||||
|
isManuallyAdded = true,
|
||||||
|
protocols = listOf(
|
||||||
|
RoomDirectoryData(
|
||||||
|
homeServer = it,
|
||||||
|
displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME,
|
||||||
|
includeAllNetworks = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,14 @@
|
|||||||
package im.vector.app.features.roomdirectory.picker
|
package im.vector.app.features.roomdirectory.picker
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryServer
|
||||||
|
|
||||||
sealed class RoomDirectoryPickerAction : VectorViewModelAction {
|
sealed class RoomDirectoryPickerAction : VectorViewModelAction {
|
||||||
object Retry : RoomDirectoryPickerAction()
|
object Retry : RoomDirectoryPickerAction()
|
||||||
|
object EnterEditMode : RoomDirectoryPickerAction()
|
||||||
|
object ExitEditMode : RoomDirectoryPickerAction()
|
||||||
|
data class SetServerUrl(val url: String) : RoomDirectoryPickerAction()
|
||||||
|
data class RemoveServer(val roomDirectoryServer: RoomDirectoryServer) : RoomDirectoryPickerAction()
|
||||||
|
|
||||||
|
object Submit : RoomDirectoryPickerAction()
|
||||||
}
|
}
|
||||||
|
@ -16,37 +16,62 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomdirectory.picker
|
package im.vector.app.features.roomdirectory.picker
|
||||||
|
|
||||||
|
import android.text.InputType
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Incomplete
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.dividerItem
|
||||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
|
import im.vector.app.core.extensions.join
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
import im.vector.app.core.ui.list.genericButtonItem
|
||||||
|
import im.vector.app.core.ui.list.verticalMarginItem
|
||||||
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
|
import im.vector.app.features.discovery.settingsContinueCancelItem
|
||||||
|
import im.vector.app.features.discovery.settingsInformationItem
|
||||||
|
import im.vector.app.features.form.formEditTextItem
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryServer
|
||||||
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
class RoomDirectoryPickerController @Inject constructor(private val stringProvider: StringProvider,
|
class RoomDirectoryPickerController @Inject constructor(
|
||||||
private val errorFormatter: ErrorFormatter,
|
private val stringProvider: StringProvider,
|
||||||
private val roomDirectoryListCreator: RoomDirectoryListCreator
|
private val colorProvider: ColorProvider,
|
||||||
|
private val dimensionConverter: DimensionConverter,
|
||||||
|
private val errorFormatter: ErrorFormatter
|
||||||
) : TypedEpoxyController<RoomDirectoryPickerViewState>() {
|
) : TypedEpoxyController<RoomDirectoryPickerViewState>() {
|
||||||
|
|
||||||
|
var currentRoomDirectoryData: RoomDirectoryData? = null
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
var index = 0
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
|
|
||||||
override fun buildModels(viewState: RoomDirectoryPickerViewState) {
|
override fun buildModels(data: RoomDirectoryPickerViewState) {
|
||||||
val host = this
|
val host = this
|
||||||
val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest
|
|
||||||
|
|
||||||
when (asyncThirdPartyProtocol) {
|
when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) {
|
||||||
is Success -> {
|
is Success -> {
|
||||||
val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol())
|
data.directories.join(
|
||||||
|
each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) },
|
||||||
directories.forEach {
|
between = { idx, _ -> buildDivider(idx) }
|
||||||
buildDirectory(it)
|
)
|
||||||
|
buildForm(data)
|
||||||
|
verticalMarginItem {
|
||||||
|
id("space_bottom")
|
||||||
|
heightInPx(host.dimensionConverter.dpToPx(16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Incomplete -> {
|
is Incomplete -> {
|
||||||
@ -64,28 +89,131 @@ class RoomDirectoryPickerController @Inject constructor(private val stringProvid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildDirectory(roomDirectoryData: RoomDirectoryData) {
|
private fun buildForm(data: RoomDirectoryPickerViewState) {
|
||||||
|
buildDivider(1000)
|
||||||
val host = this
|
val host = this
|
||||||
roomDirectoryItem {
|
if (data.inEditMode) {
|
||||||
id(host.index++)
|
verticalMarginItem {
|
||||||
|
id("form_space")
|
||||||
directoryName(roomDirectoryData.displayName)
|
heightInPx(host.dimensionConverter.dpToPx(16))
|
||||||
|
|
||||||
val description = when {
|
|
||||||
roomDirectoryData.includeAllNetworks ->
|
|
||||||
host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryData.displayName)
|
|
||||||
"Matrix" == roomDirectoryData.displayName ->
|
|
||||||
host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryData.displayName)
|
|
||||||
else ->
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
settingsInformationItem {
|
||||||
|
id("form_notice")
|
||||||
|
message(host.stringProvider.getString(R.string.directory_add_a_new_server_prompt))
|
||||||
|
colorProvider(host.colorProvider)
|
||||||
|
}
|
||||||
|
verticalMarginItem {
|
||||||
|
id("form_space_2")
|
||||||
|
heightInPx(host.dimensionConverter.dpToPx(8))
|
||||||
|
}
|
||||||
|
formEditTextItem {
|
||||||
|
id("edit")
|
||||||
|
showBottomSeparator(false)
|
||||||
|
value(data.enteredServer)
|
||||||
|
imeOptions(EditorInfo.IME_ACTION_DONE)
|
||||||
|
editorActionListener(object : TextView.OnEditorActionListener {
|
||||||
|
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
if (data.enteredServer.isNotEmpty()) {
|
||||||
|
host.callback?.onSubmitServer()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hint(host.stringProvider.getString(R.string.directory_server_placeholder))
|
||||||
|
inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI)
|
||||||
|
onTextChange { text ->
|
||||||
|
host.callback?.onEnterServerChange(text)
|
||||||
|
}
|
||||||
|
when (data.addServerAsync) {
|
||||||
|
Uninitialized -> enabled(true)
|
||||||
|
is Loading -> enabled(false)
|
||||||
|
is Success -> enabled(false)
|
||||||
|
is Fail -> {
|
||||||
|
enabled(true)
|
||||||
|
errorMessage(host.getErrorMessage(data.addServerAsync.error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (data.addServerAsync) {
|
||||||
|
Uninitialized,
|
||||||
|
is Fail -> settingsContinueCancelItem {
|
||||||
|
id("continueCancel")
|
||||||
|
continueText(host.stringProvider.getString(R.string.ok))
|
||||||
|
canContinue(data.enteredServer.isNotEmpty())
|
||||||
|
continueOnClick { host.callback?.onSubmitServer() }
|
||||||
|
cancelOnClick { host.callback?.onCancelEnterServer() }
|
||||||
|
}
|
||||||
|
is Loading -> loadingItem {
|
||||||
|
id("addLoading")
|
||||||
|
}
|
||||||
|
is Success -> Unit /* This is a transitive state */
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
genericButtonItem {
|
||||||
|
id("add")
|
||||||
|
text(host.stringProvider.getString(R.string.directory_add_a_new_server))
|
||||||
|
textColor(host.colorProvider.getColor(R.color.riotx_accent))
|
||||||
|
buttonClickAction(View.OnClickListener {
|
||||||
|
host.callback?.onStartEnterServer()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
directoryDescription(description)
|
private fun getErrorMessage(error: Throwable): String {
|
||||||
directoryAvatarUrl(roomDirectoryData.avatarUrl)
|
return if (error is Failure.ServerError
|
||||||
includeAllNetworks(roomDirectoryData.includeAllNetworks)
|
&& error.httpCode == HttpsURLConnection.HTTP_INTERNAL_ERROR /* 500 */) {
|
||||||
|
stringProvider.getString(R.string.directory_add_a_new_server_error)
|
||||||
|
} else {
|
||||||
|
errorFormatter.toHumanReadable(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
globalListener {
|
private fun buildDivider(idx: Int) {
|
||||||
host.callback?.onRoomDirectoryClicked(roomDirectoryData)
|
val host = this
|
||||||
|
dividerItem {
|
||||||
|
id("divider_$idx")
|
||||||
|
color(host.dividerColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildDirectory(roomDirectoryServer: RoomDirectoryServer) {
|
||||||
|
val host = this
|
||||||
|
roomDirectoryServerItem {
|
||||||
|
id("server_$roomDirectoryServer")
|
||||||
|
serverName(roomDirectoryServer.serverName)
|
||||||
|
canRemove(roomDirectoryServer.isManuallyAdded)
|
||||||
|
removeListener { host.callback?.onRemoveServer(roomDirectoryServer) }
|
||||||
|
|
||||||
|
if (roomDirectoryServer.isUserServer) {
|
||||||
|
serverDescription(host.stringProvider.getString(R.string.directory_your_server))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roomDirectoryServer.protocols.forEach { roomDirectoryData ->
|
||||||
|
roomDirectoryItem {
|
||||||
|
id("server_${roomDirectoryServer}_proto_$roomDirectoryData")
|
||||||
|
directoryName(
|
||||||
|
if (roomDirectoryData.includeAllNetworks) {
|
||||||
|
host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryServer.serverName)
|
||||||
|
} else {
|
||||||
|
roomDirectoryData.displayName
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (roomDirectoryData.displayName == RoomDirectoryData.MATRIX_PROTOCOL_NAME && !roomDirectoryData.includeAllNetworks) {
|
||||||
|
directoryDescription(
|
||||||
|
host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryServer.serverName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
directoryAvatarUrl(roomDirectoryData.avatarUrl)
|
||||||
|
includeAllNetworks(roomDirectoryData.includeAllNetworks)
|
||||||
|
checked(roomDirectoryData == host.currentRoomDirectoryData)
|
||||||
|
globalListener {
|
||||||
|
host.callback?.onRoomDirectoryClicked(roomDirectoryData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,5 +221,10 @@ class RoomDirectoryPickerController @Inject constructor(private val stringProvid
|
|||||||
interface Callback {
|
interface Callback {
|
||||||
fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData)
|
fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData)
|
||||||
fun retry()
|
fun retry()
|
||||||
|
fun onStartEnterServer()
|
||||||
|
fun onEnterServerChange(server: String)
|
||||||
|
fun onSubmitServer()
|
||||||
|
fun onCancelEnterServer()
|
||||||
|
fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package im.vector.app.features.roomdirectory.picker
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -28,21 +27,22 @@ import com.airbnb.mvrx.withState
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
|
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryAction
|
import im.vector.app.features.roomdirectory.RoomDirectoryAction
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryServer
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
|
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryViewModel
|
import im.vector.app.features.roomdirectory.RoomDirectoryViewModel
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO Menu to add custom room directory (not done in RiotWeb so far...)
|
|
||||||
class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerViewModelFactory: RoomDirectoryPickerViewModel.Factory,
|
class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerViewModelFactory: RoomDirectoryPickerViewModel.Factory,
|
||||||
private val roomDirectoryPickerController: RoomDirectoryPickerController
|
private val roomDirectoryPickerController: RoomDirectoryPickerController
|
||||||
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
|
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
|
||||||
|
OnBackPressed,
|
||||||
RoomDirectoryPickerController.Callback {
|
RoomDirectoryPickerController.Callback {
|
||||||
|
|
||||||
private val viewModel: RoomDirectoryViewModel by activityViewModel()
|
private val viewModel: RoomDirectoryViewModel by activityViewModel()
|
||||||
@ -65,6 +65,11 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
|
|||||||
|
|
||||||
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
|
|
||||||
|
// Give the current data to our controller. There maybe a better way to do that...
|
||||||
|
withState(viewModel) {
|
||||||
|
roomDirectoryPickerController.currentRoomDirectoryData = it.roomDirectoryData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -73,18 +78,6 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
|
|||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.menu_directory_server_picker
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
if (item.itemId == R.id.action_add_custom_hs) {
|
|
||||||
// TODO
|
|
||||||
vectorBaseActivity.notImplemented("Entering custom homeserver")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
views.roomDirectoryPickerList.configureWith(roomDirectoryPickerController)
|
views.roomDirectoryPickerList.configureWith(roomDirectoryPickerController)
|
||||||
roomDirectoryPickerController.callback = this
|
roomDirectoryPickerController.callback = this
|
||||||
@ -97,6 +90,26 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
|
|||||||
sharedActionViewModel.post(RoomDirectorySharedAction.Back)
|
sharedActionViewModel.post(RoomDirectorySharedAction.Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStartEnterServer() {
|
||||||
|
pickerViewModel.handle(RoomDirectoryPickerAction.EnterEditMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancelEnterServer() {
|
||||||
|
pickerViewModel.handle(RoomDirectoryPickerAction.ExitEditMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnterServerChange(server: String) {
|
||||||
|
pickerViewModel.handle(RoomDirectoryPickerAction.SetServerUrl(server))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSubmitServer() {
|
||||||
|
pickerViewModel.handle(RoomDirectoryPickerAction.Submit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer) {
|
||||||
|
pickerViewModel.handle(RoomDirectoryPickerAction.RemoveServer(roomDirectoryServer))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.select_room_directory)
|
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.select_room_directory)
|
||||||
@ -111,4 +124,16 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
|
|||||||
// Populate list with Epoxy
|
// Populate list with Epoxy
|
||||||
roomDirectoryPickerController.setData(state)
|
roomDirectoryPickerController.setData(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||||
|
// Leave the add server mode if started
|
||||||
|
return withState(pickerViewModel) {
|
||||||
|
if (it.inEditMode) {
|
||||||
|
pickerViewModel.handle(RoomDirectoryPickerAction.ExitEditMode)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,18 +22,28 @@ import com.airbnb.mvrx.FragmentViewModelContext
|
|||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
|
|
||||||
class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState,
|
class RoomDirectoryPickerViewModel @AssistedInject constructor(
|
||||||
private val session: Session)
|
@Assisted initialState: RoomDirectoryPickerViewState,
|
||||||
: VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
|
private val session: Session,
|
||||||
|
private val uiStateRepository: UiStateRepository,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val roomDirectoryListCreator: RoomDirectoryListCreator
|
||||||
|
) : VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
@ -50,7 +60,22 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
observeAndCompute()
|
||||||
load()
|
load()
|
||||||
|
loadCustomRoomDirectoryHomeservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeAndCompute() {
|
||||||
|
selectSubscribe(
|
||||||
|
RoomDirectoryPickerViewState::asyncThirdPartyRequest,
|
||||||
|
RoomDirectoryPickerViewState::customHomeservers
|
||||||
|
) { async, custom ->
|
||||||
|
async()?.let {
|
||||||
|
setState {
|
||||||
|
copy(directories = roomDirectoryListCreator.computeDirectories(it, custom))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun load() {
|
private fun load() {
|
||||||
@ -71,9 +96,101 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadCustomRoomDirectoryHomeservers() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
customHomeservers = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomDirectoryPickerAction) {
|
override fun handle(action: RoomDirectoryPickerAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
RoomDirectoryPickerAction.Retry -> load()
|
RoomDirectoryPickerAction.Retry -> load()
|
||||||
|
RoomDirectoryPickerAction.EnterEditMode -> handleEnterEditMode()
|
||||||
|
RoomDirectoryPickerAction.ExitEditMode -> handleExitEditMode()
|
||||||
|
is RoomDirectoryPickerAction.SetServerUrl -> handleSetServerUrl(action)
|
||||||
|
RoomDirectoryPickerAction.Submit -> handleSubmit()
|
||||||
|
is RoomDirectoryPickerAction.RemoveServer -> handleRemoveServer(action)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleEnterEditMode() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
inEditMode = true,
|
||||||
|
enteredServer = "",
|
||||||
|
addServerAsync = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleExitEditMode() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
inEditMode = false,
|
||||||
|
enteredServer = "",
|
||||||
|
addServerAsync = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSetServerUrl(action: RoomDirectoryPickerAction.SetServerUrl) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
enteredServer = action.url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSubmit() = withState { state ->
|
||||||
|
// First avoid duplicate
|
||||||
|
val enteredServer = state.enteredServer
|
||||||
|
|
||||||
|
val existingServerList = state.directories.map { it.serverName }
|
||||||
|
|
||||||
|
if (enteredServer in existingServerList) {
|
||||||
|
setState {
|
||||||
|
copy(addServerAsync = Fail(Throwable(stringProvider.getString(R.string.directory_add_a_new_server_error_already_added))))
|
||||||
|
}
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
setState {
|
||||||
|
copy(addServerAsync = Loading())
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
session.getPublicRooms(
|
||||||
|
server = enteredServer,
|
||||||
|
publicRoomsParams = PublicRoomsParams(limit = 1)
|
||||||
|
)
|
||||||
|
// Success, let add the server to our local repository, and update the state
|
||||||
|
val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + enteredServer
|
||||||
|
uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
inEditMode = false,
|
||||||
|
enteredServer = "",
|
||||||
|
addServerAsync = Uninitialized,
|
||||||
|
customHomeservers = newSet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(addServerAsync = Fail(failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRemoveServer(action: RoomDirectoryPickerAction.RemoveServer) {
|
||||||
|
val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) - action.roomDirectoryServer.serverName
|
||||||
|
uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
customHomeservers = newSet
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,15 @@ package im.vector.app.features.roomdirectory.picker
|
|||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryServer
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
|
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||||
|
|
||||||
data class RoomDirectoryPickerViewState(
|
data class RoomDirectoryPickerViewState(
|
||||||
val asyncThirdPartyRequest: Async<Map<String, ThirdPartyProtocol>> = Uninitialized
|
val asyncThirdPartyRequest: Async<Map<String, ThirdPartyProtocol>> = Uninitialized,
|
||||||
|
val customHomeservers: Set<String> = emptySet(),
|
||||||
|
val inEditMode: Boolean = false,
|
||||||
|
val enteredServer: String = "",
|
||||||
|
val addServerAsync: Async<Unit> = Uninitialized,
|
||||||
|
// computed
|
||||||
|
val directories: List<RoomDirectoryServer> = emptyList()
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomdirectory.picker
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.epoxy.onClick
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_room_directory_server)
|
||||||
|
abstract class RoomDirectoryServerItem : VectorEpoxyModel<RoomDirectoryServerItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var serverName: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var serverDescription: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var canRemove: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var removeListener: ClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.nameView.text = serverName
|
||||||
|
holder.descriptionView.setTextOrHide(serverDescription)
|
||||||
|
holder.deleteView.isVisible = canRemove
|
||||||
|
holder.deleteView.onClick(removeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val nameView by bind<TextView>(R.id.itemRoomDirectoryServerName)
|
||||||
|
val descriptionView by bind<TextView>(R.id.itemRoomDirectoryServerDescription)
|
||||||
|
val deleteView by bind<View>(R.id.itemRoomDirectoryServerRemove)
|
||||||
|
}
|
||||||
|
}
|
@ -25,9 +25,9 @@ import im.vector.app.core.extensions.addFragment
|
|||||||
import im.vector.app.core.platform.ToolbarConfigurable
|
import im.vector.app.core.platform.ToolbarConfigurable
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivitySimpleBinding
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -68,16 +68,14 @@ class RoomSettingsController @Inject constructor(
|
|||||||
id("avatar")
|
id("avatar")
|
||||||
enabled(data.actionPermissions.canChangeAvatar)
|
enabled(data.actionPermissions.canChangeAvatar)
|
||||||
when (val avatarAction = data.avatarAction) {
|
when (val avatarAction = data.avatarAction) {
|
||||||
RoomSettingsViewState.AvatarAction.None -> {
|
RoomSettingsViewState.AvatarAction.None -> {
|
||||||
// Use the current value
|
// Use the current value
|
||||||
avatarRenderer(host.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
|
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
|
||||||
matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl))
|
matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl))
|
||||||
}
|
}
|
||||||
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
|
RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null)
|
||||||
imageUri(null)
|
is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri)
|
||||||
is RoomSettingsViewState.AvatarAction.UpdateAvatar ->
|
|
||||||
imageUri(avatarAction.newAvatarUri)
|
|
||||||
}
|
}
|
||||||
clickListener { host.callback?.onAvatarChange() }
|
clickListener { host.callback?.onAvatarChange() }
|
||||||
deleteListener { host.callback?.onAvatarDelete() }
|
deleteListener { host.callback?.onAvatarDelete() }
|
||||||
@ -102,6 +100,7 @@ class RoomSettingsController @Inject constructor(
|
|||||||
id("topic")
|
id("topic")
|
||||||
enabled(data.actionPermissions.canChangeTopic)
|
enabled(data.actionPermissions.canChangeTopic)
|
||||||
value(data.newTopic ?: roomSummary.topic)
|
value(data.newTopic ?: roomSummary.topic)
|
||||||
|
singleLine(false)
|
||||||
hint(host.stringProvider.getString(R.string.room_settings_topic_hint))
|
hint(host.stringProvider.getString(R.string.room_settings_topic_hint))
|
||||||
|
|
||||||
onTextChange { text ->
|
onTextChange { text ->
|
||||||
|
@ -96,7 +96,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
|
|||||||
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
val roomSummary = session.getRoomSummary(spaceArgs.spaceId)
|
val roomSummary = session.getRoomSummary(spaceArgs.spaceId)
|
||||||
roomSummary?.toMatrixItem()?.let {
|
roomSummary?.toMatrixItem()?.let {
|
||||||
avatarRenderer.renderSpace(it, views.spaceAvatarImageView)
|
avatarRenderer.render(it, views.spaceAvatarImageView)
|
||||||
}
|
}
|
||||||
views.spaceNameView.text = roomSummary?.displayName
|
views.spaceNameView.text = roomSummary?.displayName
|
||||||
views.spaceDescription.setTextOrHide(roomSummary?.topic?.takeIf { it.isNotEmpty() })
|
views.spaceDescription.setTextOrHide(roomSummary?.topic?.takeIf { it.isNotEmpty() })
|
||||||
|
@ -87,7 +87,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
|
|||||||
holder.indentSpace.isVisible = indent > 0
|
holder.indentSpace.isVisible = indent > 0
|
||||||
holder.separator.isVisible = showSeparator
|
holder.separator.isVisible = showSeparator
|
||||||
|
|
||||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
holder.counterBadgeView.render(countState)
|
holder.counterBadgeView.render(countState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ abstract class SubSpaceSummaryItem : VectorEpoxyModel<SubSpaceSummaryItem.Holder
|
|||||||
width = indent * 30
|
width = indent * 30
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
holder.counterBadgeView.render(countState)
|
holder.counterBadgeView.render(countState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
|
|||||||
id("roomName1")
|
id("roomName1")
|
||||||
enabled(true)
|
enabled(true)
|
||||||
value(firstRoomName)
|
value(firstRoomName)
|
||||||
singleLine(true)
|
|
||||||
hint(host.stringProvider.getString(R.string.create_room_name_section))
|
hint(host.stringProvider.getString(R.string.create_room_name_section))
|
||||||
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
|
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
@ -83,7 +82,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
|
|||||||
id("roomName2")
|
id("roomName2")
|
||||||
enabled(true)
|
enabled(true)
|
||||||
value(secondRoomName)
|
value(secondRoomName)
|
||||||
singleLine(true)
|
|
||||||
hint(host.stringProvider.getString(R.string.create_room_name_section))
|
hint(host.stringProvider.getString(R.string.create_room_name_section))
|
||||||
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
|
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
@ -97,7 +95,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
|
|||||||
id("roomName3")
|
id("roomName3")
|
||||||
enabled(true)
|
enabled(true)
|
||||||
value(thirdRoomName)
|
value(thirdRoomName)
|
||||||
singleLine(true)
|
|
||||||
hint(host.stringProvider.getString(R.string.create_room_name_section))
|
hint(host.stringProvider.getString(R.string.create_room_name_section))
|
||||||
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
|
endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT)
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
|
@ -54,7 +54,7 @@ class SpaceDetailEpoxyController @Inject constructor(
|
|||||||
enabled(true)
|
enabled(true)
|
||||||
imageUri(data?.avatarUri)
|
imageUri(data?.avatarUri)
|
||||||
avatarRenderer(host.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
matrixItem(data?.name?.let { MatrixItem.RoomItem("!", it, null).takeIf { !it.displayName.isNullOrBlank() } })
|
matrixItem(data?.name?.let { MatrixItem.SpaceItem("!", it, null).takeIf { !it.displayName.isNullOrBlank() } })
|
||||||
clickListener { host.listener?.onAvatarChange() }
|
clickListener { host.listener?.onAvatarChange() }
|
||||||
deleteListener { host.listener?.onAvatarDelete() }
|
deleteListener { host.listener?.onAvatarDelete() }
|
||||||
}
|
}
|
||||||
@ -64,7 +64,6 @@ class SpaceDetailEpoxyController @Inject constructor(
|
|||||||
enabled(true)
|
enabled(true)
|
||||||
value(data?.name)
|
value(data?.name)
|
||||||
hint(host.stringProvider.getString(R.string.create_room_name_hint))
|
hint(host.stringProvider.getString(R.string.create_room_name_hint))
|
||||||
singleLine(true)
|
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
errorMessage(data?.nameInlineError)
|
errorMessage(data?.nameInlineError)
|
||||||
// onBind { _, view, _ ->
|
// onBind { _, view, _ ->
|
||||||
|
@ -122,13 +122,15 @@ class SpaceDirectoryController @Inject constructor(
|
|||||||
val isSpace = info.roomType == RoomType.SPACE
|
val isSpace = info.roomType == RoomType.SPACE
|
||||||
val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true
|
val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true
|
||||||
val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false
|
val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false
|
||||||
|
// if it's known use that matrixItem because it would have a better computed name
|
||||||
|
val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem()
|
||||||
|
?: info.toMatrixItem()
|
||||||
spaceChildInfoItem {
|
spaceChildInfoItem {
|
||||||
id(info.childRoomId)
|
id(info.childRoomId)
|
||||||
matrixItem(info.toMatrixItem())
|
matrixItem(matrixItem)
|
||||||
avatarRenderer(host.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
topic(info.topic)
|
topic(info.topic)
|
||||||
memberCount(info.activeMemberCount ?: 0)
|
memberCount(info.activeMemberCount ?: 0)
|
||||||
space(isSpace)
|
|
||||||
loading(isLoading)
|
loading(isLoading)
|
||||||
buttonLabel(
|
buttonLabel(
|
||||||
if (isJoined) host.stringProvider.getString(R.string.action_open)
|
if (isJoined) host.stringProvider.getString(R.string.action_open)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.app.features.spaces.explore
|
package im.vector.app.features.spaces.explore
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -23,19 +24,33 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.text.toSpannable
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.dialogs.withColoredButton
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.OnBackPressed
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.utils.colorizeMatchingText
|
||||||
|
import im.vector.app.core.utils.isValidUrl
|
||||||
|
import im.vector.app.core.utils.openUrlInExternalBrowser
|
||||||
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
|
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.app.features.matrixto.SpaceCardRenderer
|
||||||
|
import im.vector.app.features.permalink.PermalinkHandler
|
||||||
import im.vector.app.features.spaces.manage.ManageType
|
import im.vector.app.features.spaces.manage.ManageType
|
||||||
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
|
import java.net.URL
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@ -44,9 +59,13 @@ data class SpaceDirectoryArgs(
|
|||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
class SpaceDirectoryFragment @Inject constructor(
|
class SpaceDirectoryFragment @Inject constructor(
|
||||||
private val epoxyController: SpaceDirectoryController
|
private val epoxyController: SpaceDirectoryController,
|
||||||
|
private val permalinkHandler: PermalinkHandler,
|
||||||
|
private val spaceCardRenderer: SpaceCardRenderer,
|
||||||
|
private val colorProvider: ColorProvider
|
||||||
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
|
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
|
||||||
SpaceDirectoryController.InteractionListener,
|
SpaceDirectoryController.InteractionListener,
|
||||||
|
TimelineEventController.UrlClickCallback,
|
||||||
OnBackPressed {
|
OnBackPressed {
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.menu_space_directory
|
override fun getMenuRes() = R.menu.menu_space_directory
|
||||||
@ -71,6 +90,9 @@ class SpaceDirectoryFragment @Inject constructor(
|
|||||||
viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) {
|
viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views.spaceCard.matrixToCardMainButton.isVisible = false
|
||||||
|
views.spaceCard.matrixToCardSecondaryButton.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -82,10 +104,21 @@ class SpaceDirectoryFragment @Inject constructor(
|
|||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.setData(state)
|
epoxyController.setData(state)
|
||||||
|
|
||||||
val title = state.hierarchyStack.lastOrNull()?.let { currentParent ->
|
val currentParent = state.hierarchyStack.lastOrNull()?.let { currentParent ->
|
||||||
state.spaceSummaryApiResult.invoke()?.firstOrNull { it.childRoomId == currentParent }
|
state.spaceSummaryApiResult.invoke()?.firstOrNull { it.childRoomId == currentParent }
|
||||||
}?.name ?: getString(R.string.space_explore_activity_title)
|
}
|
||||||
views.toolbar.title = title
|
|
||||||
|
if (currentParent == null) {
|
||||||
|
val title = getString(R.string.space_explore_activity_title)
|
||||||
|
views.toolbar.title = title
|
||||||
|
|
||||||
|
spaceCardRenderer.render(state.spaceSummary.invoke(), emptyList(), this, views.spaceCard)
|
||||||
|
} else {
|
||||||
|
val title = currentParent.name ?: currentParent.canonicalAlias ?: getString(R.string.space_explore_activity_title)
|
||||||
|
views.toolbar.title = title
|
||||||
|
|
||||||
|
spaceCardRenderer.render(currentParent, emptyList(), this, views.spaceCard)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->
|
override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->
|
||||||
@ -96,7 +129,7 @@ class SpaceDirectoryFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.spaceAddRoom -> {
|
R.id.spaceAddRoom -> {
|
||||||
withState(viewModel) { state ->
|
withState(viewModel) { state ->
|
||||||
addExistingRooms(state.spaceId)
|
addExistingRooms(state.spaceId)
|
||||||
}
|
}
|
||||||
@ -138,6 +171,44 @@ class SpaceDirectoryFragment @Inject constructor(
|
|||||||
override fun addExistingRooms(spaceId: String) {
|
override fun addExistingRooms(spaceId: String) {
|
||||||
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms))
|
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUrlClicked(url: String, title: String): Boolean {
|
||||||
|
permalinkHandler
|
||||||
|
.launch(requireActivity(), url, null)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { managed ->
|
||||||
|
if (!managed) {
|
||||||
|
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.external_link_confirmation_title)
|
||||||
|
.setMessage(
|
||||||
|
getString(R.string.external_link_confirmation_message, title, url)
|
||||||
|
.toSpannable()
|
||||||
|
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
|
||||||
|
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
|
||||||
|
)
|
||||||
|
.setPositiveButton(R.string._continue) { _, _ ->
|
||||||
|
openUrlInExternalBrowser(requireContext(), url)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||||
|
} else {
|
||||||
|
// Open in external browser, in a new Tab
|
||||||
|
openUrlInExternalBrowser(requireContext(), url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnDestroyView()
|
||||||
|
// In fact it is always managed
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUrlLongClicked(url: String): Boolean {
|
||||||
|
// nothing?
|
||||||
|
return false
|
||||||
|
}
|
||||||
// override fun navigateToRoom(roomId: String) {
|
// override fun navigateToRoom(roomId: String) {
|
||||||
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
|
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
|
||||||
// }
|
// }
|
||||||
|
@ -37,7 +37,9 @@ data class SpaceDirectoryState(
|
|||||||
val joinedRoomsIds: Set<String> = emptySet(),
|
val joinedRoomsIds: Set<String> = emptySet(),
|
||||||
// keys are room alias or roomId
|
// keys are room alias or roomId
|
||||||
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
|
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
|
||||||
val canAddRooms: Boolean = false
|
val canAddRooms: Boolean = false,
|
||||||
|
// cached room summaries of known rooms
|
||||||
|
val knownRoomSummaries : List<RoomSummary> = emptyList()
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
constructor(args: SpaceDirectoryArgs) : this(
|
constructor(args: SpaceDirectoryArgs) : this(
|
||||||
spaceId = args.spaceId
|
spaceId = args.spaceId
|
||||||
|
@ -66,7 +66,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
|
|||||||
val spaceSum = session.getRoomSummary(initialState.spaceId)
|
val spaceSum = session.getRoomSummary(initialState.spaceId)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
childList = spaceSum?.spaceChildren ?: emptyList()
|
childList = spaceSum?.spaceChildren ?: emptyList(),
|
||||||
|
spaceSummary = spaceSum?.let { Success(spaceSum) } ?: Loading()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,9 +102,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val query = session.spaceService().querySpaceChildren(initialState.spaceId)
|
val query = session.spaceService().querySpaceChildren(initialState.spaceId)
|
||||||
|
val knownSummaries = query.second.mapNotNull {
|
||||||
|
session.getRoomSummary(it.childRoomId)
|
||||||
|
?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced)
|
||||||
|
}
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
spaceSummaryApiResult = Success(query.second)
|
spaceSummaryApiResult = Success(query.second),
|
||||||
|
knownRoomSummaries = knownSummaries
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
@ -148,7 +154,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
|
|||||||
copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId))
|
copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SpaceDirectoryViewAction.HandleBack -> {
|
SpaceDirectoryViewAction.HandleBack -> {
|
||||||
withState {
|
withState {
|
||||||
if (it.hierarchyStack.isEmpty()) {
|
if (it.hierarchyStack.isEmpty()) {
|
||||||
_viewEvents.post(SpaceDirectoryViewEvents.Dismiss)
|
_viewEvents.post(SpaceDirectoryViewEvents.Dismiss)
|
||||||
@ -161,20 +167,20 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SpaceDirectoryViewAction.JoinOrOpen -> {
|
is SpaceDirectoryViewAction.JoinOrOpen -> {
|
||||||
handleJoinOrOpen(action.spaceChildInfo)
|
handleJoinOrOpen(action.spaceChildInfo)
|
||||||
}
|
}
|
||||||
is SpaceDirectoryViewAction.NavigateToRoom -> {
|
is SpaceDirectoryViewAction.NavigateToRoom -> {
|
||||||
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId))
|
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId))
|
||||||
}
|
}
|
||||||
is SpaceDirectoryViewAction.ShowDetails -> {
|
is SpaceDirectoryViewAction.ShowDetails -> {
|
||||||
// This is temporary for now to at least display something for the space beta
|
// This is temporary for now to at least display something for the space beta
|
||||||
// It's not ideal as it's doing some peeking that is not needed.
|
// It's not ideal as it's doing some peeking that is not needed.
|
||||||
session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let {
|
session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let {
|
||||||
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it))
|
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SpaceDirectoryViewAction.Retry -> {
|
SpaceDirectoryViewAction.Retry -> {
|
||||||
refreshFromApi()
|
refreshFromApi()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import android.os.Parcelable
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
@ -33,12 +32,12 @@ import com.airbnb.mvrx.fragmentViewModel
|
|||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ScreenComponent
|
import im.vector.app.core.di.ScreenComponent
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
|
||||||
import im.vector.app.core.platform.ButtonStateView
|
import im.vector.app.core.platform.ButtonStateView
|
||||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.app.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding
|
import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.matrixto.SpaceCardRenderer
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -60,6 +59,9 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var avatarRenderer: AvatarRenderer
|
lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var spaceCardRenderer: SpaceCardRenderer
|
||||||
|
|
||||||
private val viewModel: SpaceInviteBottomSheetViewModel by fragmentViewModel(SpaceInviteBottomSheetViewModel::class)
|
private val viewModel: SpaceInviteBottomSheetViewModel by fragmentViewModel(SpaceInviteBottomSheetViewModel::class)
|
||||||
|
|
||||||
@Inject lateinit var viewModelFactory: SpaceInviteBottomSheetViewModel.Factory
|
@Inject lateinit var viewModelFactory: SpaceInviteBottomSheetViewModel.Factory
|
||||||
@ -133,12 +135,7 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
|
|||||||
views.inviterMxid.isVisible = false
|
views.inviterMxid.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
views.spaceCard.matrixToCardContentVisibility.isVisible = true
|
spaceCardRenderer.render(summary, state.peopleYouKnow.invoke().orEmpty(), null, views.spaceCard)
|
||||||
summary?.toMatrixItem()?.let { avatarRenderer.renderSpace(it, views.spaceCard.matrixToCardAvatar) }
|
|
||||||
views.spaceCard.matrixToCardNameText.text = summary?.displayName
|
|
||||||
views.spaceCard.matrixToBetaTag.isVisible = true
|
|
||||||
views.spaceCard.matrixToCardAliasText.setTextOrHide(summary?.canonicalAlias)
|
|
||||||
views.spaceCard.matrixToCardDescText.setTextOrHide(summary?.topic)
|
|
||||||
|
|
||||||
views.spaceCard.matrixToCardMainButton.button.text = getString(R.string.accept)
|
views.spaceCard.matrixToCardMainButton.button.text = getString(R.string.accept)
|
||||||
views.spaceCard.matrixToCardSecondaryButton.button.text = getString(R.string.decline)
|
views.spaceCard.matrixToCardSecondaryButton.button.text = getString(R.string.decline)
|
||||||
@ -178,40 +175,6 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
|
|||||||
views.spaceCard.matrixToCardSecondaryButton.button.isEnabled = true
|
views.spaceCard.matrixToCardSecondaryButton.button.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val memberCount = summary?.otherMemberIds?.size ?: 0
|
|
||||||
if (memberCount != 0) {
|
|
||||||
views.spaceCard.matrixToMemberPills.isVisible = true
|
|
||||||
views.spaceCard.spaceChildMemberCountText.text = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
|
|
||||||
} else {
|
|
||||||
// hide the pill
|
|
||||||
views.spaceCard.matrixToMemberPills.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
val peopleYouKnow = state.peopleYouKnow.invoke().orEmpty()
|
|
||||||
|
|
||||||
val images = listOf(
|
|
||||||
views.spaceCard.knownMember1,
|
|
||||||
views.spaceCard.knownMember2,
|
|
||||||
views.spaceCard.knownMember3,
|
|
||||||
views.spaceCard.knownMember4,
|
|
||||||
views.spaceCard.knownMember5
|
|
||||||
).onEach { it.isGone = true }
|
|
||||||
|
|
||||||
if (peopleYouKnow.isEmpty()) {
|
|
||||||
views.spaceCard.peopleYouMayKnowText.isVisible = false
|
|
||||||
} else {
|
|
||||||
peopleYouKnow.forEachIndexed { index, item ->
|
|
||||||
images[index].isVisible = true
|
|
||||||
avatarRenderer.render(item.toMatrixItem(), images[index])
|
|
||||||
}
|
|
||||||
views.spaceCard.peopleYouMayKnowText.setTextOrHide(
|
|
||||||
resources.getQuantityString(R.plurals.space_people_you_know,
|
|
||||||
peopleYouKnow.count(),
|
|
||||||
peopleYouKnow.count()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetInvitedToSpaceBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetInvitedToSpaceBinding {
|
||||||
|
@ -27,7 +27,6 @@ import im.vector.app.features.home.AvatarRenderer
|
|||||||
import im.vector.app.features.home.room.list.RoomCategoryItem_
|
import im.vector.app.features.home.room.list.RoomCategoryItem_
|
||||||
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -155,7 +154,6 @@ class AddRoomListController @Inject constructor(
|
|||||||
id(item.roomId)
|
id(item.roomId)
|
||||||
matrixItem(item.toMatrixItem())
|
matrixItem(item.toMatrixItem())
|
||||||
avatarRenderer(host.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
space(item.roomType == RoomType.SPACE)
|
|
||||||
selected(host.selectedItems[item.roomId] ?: false)
|
selected(host.selectedItems[item.roomId] ?: false)
|
||||||
itemClickListener(DebouncedClickListener({
|
itemClickListener(DebouncedClickListener({
|
||||||
host.listener?.onItemSelected(item)
|
host.listener?.onItemSelected(item)
|
||||||
|
@ -34,18 +34,14 @@ abstract class RoomManageSelectionItem : VectorEpoxyModel<RoomManageSelectionIte
|
|||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute var space: Boolean = false
|
|
||||||
@EpoxyAttribute var selected: Boolean = false
|
@EpoxyAttribute var selected: Boolean = false
|
||||||
@EpoxyAttribute var suggested: Boolean = false
|
@EpoxyAttribute var suggested: Boolean = false
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
if (space) {
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
|
||||||
} else {
|
|
||||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
|
||||||
}
|
|
||||||
holder.titleText.text = matrixItem.getBestName()
|
holder.titleText.text = matrixItem.getBestName()
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
|
@ -33,17 +33,13 @@ abstract class RoomSelectionItem : VectorEpoxyModel<RoomSelectionItem.Holder>()
|
|||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute var space: Boolean = false
|
|
||||||
@EpoxyAttribute var selected: Boolean = false
|
@EpoxyAttribute var selected: Boolean = false
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
if (space) {
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
|
|
||||||
} else {
|
|
||||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
|
||||||
}
|
|
||||||
holder.titleText.text = matrixItem.getBestName()
|
holder.titleText.text = matrixItem.getBestName()
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
|
@ -27,7 +27,6 @@ import im.vector.app.core.resources.StringProvider
|
|||||||
import im.vector.app.core.ui.list.genericFooterItem
|
import im.vector.app.core.ui.list.genericFooterItem
|
||||||
import im.vector.app.core.utils.DebouncedClickListener
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -83,7 +82,6 @@ class SpaceManageRoomsController @Inject constructor(
|
|||||||
matrixItem(childInfo.toMatrixItem())
|
matrixItem(childInfo.toMatrixItem())
|
||||||
avatarRenderer(host.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
suggested(childInfo.suggested ?: false)
|
suggested(childInfo.suggested ?: false)
|
||||||
space(childInfo.roomType == RoomType.SPACE)
|
|
||||||
selected(data.selectedRooms.contains(childInfo.childRoomId))
|
selected(data.selectedRooms.contains(childInfo.childRoomId))
|
||||||
itemClickListener(DebouncedClickListener({
|
itemClickListener(DebouncedClickListener({
|
||||||
host.listener?.toggleSelection(childInfo)
|
host.listener?.toggleSelection(childInfo)
|
||||||
|
@ -71,7 +71,7 @@ class SpaceSettingsController @Inject constructor(
|
|||||||
// Use the current value
|
// Use the current value
|
||||||
avatarRenderer(host.avatarRenderer)
|
avatarRenderer(host.avatarRenderer)
|
||||||
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
|
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
|
||||||
matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl))
|
matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl))
|
||||||
}
|
}
|
||||||
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
|
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
|
||||||
imageUri(null)
|
imageUri(null)
|
||||||
|
@ -139,7 +139,7 @@ class SpaceSettingsFragment @Inject constructor(
|
|||||||
drawableProvider.getDrawable(R.drawable.ic_beta_pill),
|
drawableProvider.getDrawable(R.drawable.ic_beta_pill),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
avatarRenderer.renderSpace(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
|
avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
|
||||||
views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
|
views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user