mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 02:45:53 +03:00
Merge pull request #1665 from vector-im/feature/fix_small_issues
Feature/fix small issues
This commit is contained in:
commit
9c402d4d40
93 changed files with 643 additions and 884 deletions
|
@ -13,6 +13,11 @@ Improvements 🙌:
|
|||
- Set up SSSS from security settings (#1567)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Integration Manager: Wrong URL to review terms if URL in config contains path (#1606)
|
||||
- Regression Composer does not grow, crops out text (#1650)
|
||||
- Bug / Unwanted draft (#698)
|
||||
- All users seems to be able to see the enable encryption option in room settings (#1341)
|
||||
- Leave room only leaves the current version (#1656)
|
||||
- Regression | Share action menu do not work (#1647)
|
||||
- verification issues on transition (#1555)
|
||||
- Fix issue when restoring keys backup using recovery key
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
|
|||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
|
@ -173,6 +174,10 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveRoomChangeMembershipState(): Observable<Map<String, ChangeMembershipState>> {
|
||||
return session.getChangeMembershipsLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
|
||||
return Observable.combineLatest<List<UserAccountData>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
||||
liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
|
||||
|
|
|
@ -39,5 +39,10 @@ data class UnsignedData(
|
|||
* Optional. The previous content for this event. If there is no previous content, this key will be missing.
|
||||
*/
|
||||
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null,
|
||||
@Json(name = "m.relations") val relations: AggregatedRelations? = null
|
||||
@Json(name = "m.relations") val relations: AggregatedRelations? = null,
|
||||
/**
|
||||
* Optional. The eventId of the previous state event being replaced.
|
||||
*/
|
||||
@Json(name = "replaces_state") val replacesState: String? = null
|
||||
|
||||
)
|
||||
|
|
|
@ -34,13 +34,6 @@ interface RoomDirectoryService {
|
|||
publicRoomsParams: PublicRoomsParams,
|
||||
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
|
||||
|
||||
/**
|
||||
* Join a room by id, or room alias
|
||||
*/
|
||||
fun joinRoom(roomIdOrAlias: String,
|
||||
reason: String? = null,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Fetches the overall metadata about protocols supported by the homeserver.
|
||||
* Includes both the available protocols and all fields required for queries against each protocol.
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
@ -104,5 +105,13 @@ interface RoomService {
|
|||
searchOnServer: Boolean,
|
||||
callback: MatrixCallback<Optional<String>>): Cancelable
|
||||
|
||||
/**
|
||||
* Return a live data of all local changes membership that happened since the session has been opened.
|
||||
* It allows you to track this in your client to known what is currently being processed by the SDK.
|
||||
* It won't know anything about change being done in other client.
|
||||
* Keys are roomId or roomAlias, depending of what you used as parameter for the join/leave action
|
||||
*/
|
||||
fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>>
|
||||
|
||||
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {
|
|||
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
|
||||
*/
|
||||
data class RoomSummaryQueryParams(
|
||||
val roomId: QueryStringValue,
|
||||
val displayName: QueryStringValue,
|
||||
val canonicalAlias: QueryStringValue,
|
||||
val memberships: List<Membership>
|
||||
|
@ -35,11 +36,13 @@ data class RoomSummaryQueryParams(
|
|||
|
||||
class Builder {
|
||||
|
||||
var roomId: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
||||
var memberships: List<Membership> = Membership.all()
|
||||
|
||||
fun build() = RoomSummaryQueryParams(
|
||||
roomId = roomId,
|
||||
displayName = displayName,
|
||||
canonicalAlias = canonicalAlias,
|
||||
memberships = memberships
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.members
|
||||
|
||||
sealed class ChangeMembershipState() {
|
||||
object Unknown : ChangeMembershipState()
|
||||
object Joining : ChangeMembershipState()
|
||||
data class FailedJoining(val throwable: Throwable) : ChangeMembershipState()
|
||||
object Joined : ChangeMembershipState()
|
||||
object Leaving : ChangeMembershipState()
|
||||
data class FailedLeaving(val throwable: Throwable) : ChangeMembershipState()
|
||||
object Left : ChangeMembershipState()
|
||||
|
||||
fun isInProgress() = this is Joining || this is Leaving
|
||||
|
||||
fun isSuccessful() = this is Joined || this is Left
|
||||
|
||||
fun isFailed() = this is FailedJoining || this is FailedLeaving
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.room.powerlevels
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
|
||||
/**
|
||||
|
@ -124,59 +123,4 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
else -> Role.Moderator.value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user have the necessary power level to change room name
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to change room name
|
||||
*/
|
||||
fun isUserAbleToChangeRoomName(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_NAME] ?: powerLevelsContent.stateDefault
|
||||
return powerLevel >= minPowerLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user have the necessary power level to change room topic
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to change room topic
|
||||
*/
|
||||
fun isUserAbleToChangeRoomTopic(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_TOPIC] ?: powerLevelsContent.stateDefault
|
||||
return powerLevel >= minPowerLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user have the necessary power level to change room canonical alias
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to change room canonical alias
|
||||
*/
|
||||
fun isUserAbleToChangeRoomCanonicalAlias(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_CANONICAL_ALIAS] ?: powerLevelsContent.stateDefault
|
||||
return powerLevel >= minPowerLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user have the necessary power level to change room history readability
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to change room history readability
|
||||
*/
|
||||
fun isUserAbleToChangeRoomHistoryReadability(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_HISTORY_VISIBILITY] ?: powerLevelsContent.stateDefault
|
||||
return powerLevel >= minPowerLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user have the necessary power level to change room avatar
|
||||
* @param userId the id of the user to check for.
|
||||
* @return true if able to change room avatar
|
||||
*/
|
||||
fun isUserAbleToChangeRoomAvatar(userId: String): Boolean {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minPowerLevel = powerLevelsContent.events[EventType.STATE_ROOM_AVATAR] ?: powerLevelsContent.stateDefault
|
||||
return powerLevel >= minPowerLevel
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||
it.process(realm, domainEvent)
|
||||
}
|
||||
}
|
||||
realm.where(EventInsertEntity::class.java).findAll().deleteAllFromRealm()
|
||||
realm.delete(EventInsertEntity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,8 +88,8 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
} catch (e: MXCryptoError) {
|
||||
Timber.v("Call service: Failed to decrypt event")
|
||||
// TODO -> we should keep track of this and retry, or aggregation will be broken
|
||||
Timber.v("Failed to decrypt event")
|
||||
// TODO -> we should keep track of this and retry, or some processing will never be handled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,17 +123,18 @@ private fun computeIsUnique(
|
|||
realm: Realm,
|
||||
roomId: String,
|
||||
isLastForward: Boolean,
|
||||
myRoomMemberContent: RoomMemberContent,
|
||||
senderRoomMemberContent: RoomMemberContent,
|
||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>
|
||||
): Boolean {
|
||||
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
||||
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
|
||||
it != senderRoomMemberContent && it?.displayName == senderRoomMemberContent.displayName
|
||||
} == null
|
||||
return if (isLastForward) {
|
||||
val isLiveUnique = RoomMemberSummaryEntity
|
||||
.where(realm, roomId)
|
||||
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
|
||||
.findAll().none {
|
||||
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, senderRoomMemberContent.displayName)
|
||||
.findAll()
|
||||
.none {
|
||||
!roomMemberContentsByUser.containsKey(it.userId)
|
||||
}
|
||||
isHistoricalUnique && isLiveUnique
|
||||
|
|
|
@ -24,7 +24,7 @@ import io.realm.annotations.PrimaryKey
|
|||
internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "",
|
||||
@Index var userId: String = "",
|
||||
@Index var roomId: String = "",
|
||||
var displayName: String? = null,
|
||||
@Index var displayName: String? = null,
|
||||
var avatarUrl: String? = null,
|
||||
var reason: String? = null,
|
||||
var isDirect: Boolean = false
|
||||
|
|
|
@ -24,13 +24,11 @@ import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProt
|
|||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask,
|
||||
private val joinRoomTask: JoinRoomTask,
|
||||
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
|
||||
private val taskExecutor: TaskExecutor) : RoomDirectoryService {
|
||||
|
||||
|
@ -44,14 +42,6 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun joinRoom(roomIdOrAlias: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return joinRoomTask
|
||||
.configureWith(JoinRoomTask.Params(roomIdOrAlias, reason)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable {
|
||||
return getThirdPartyProtocolsTask
|
||||
.configureWith {
|
||||
|
|
|
@ -21,12 +21,14 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryDataSource
|
||||
|
@ -43,6 +45,7 @@ internal class DefaultRoomService @Inject constructor(
|
|||
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||
private val roomGetter: RoomGetter,
|
||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : RoomService {
|
||||
|
||||
|
@ -111,4 +114,8 @@ internal class DefaultRoomService @Inject constructor(
|
|||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
|
||||
return roomChangeMembershipStateDataSource.getLiveStates()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
|
|||
return
|
||||
}
|
||||
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
|
||||
when (event.getClearType()) {
|
||||
when (event.type) {
|
||||
EventType.REACTION -> {
|
||||
// we got a reaction!!
|
||||
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
|
||||
|
@ -161,7 +161,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
|
|||
if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE
|
||||
|| encryptedEventContent?.relatesTo?.type == RelationType.RESPONSE
|
||||
) {
|
||||
// we need to decrypt if needed
|
||||
event.getClearContent().toModel<MessageContent>()?.let {
|
||||
if (encryptedEventContent.relatesTo.type == RelationType.REPLACE) {
|
||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.room.membership
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This class holds information about rooms that current user is joining or leaving.
|
||||
*/
|
||||
@SessionScope
|
||||
internal class RoomChangeMembershipStateDataSource @Inject constructor() {
|
||||
|
||||
private val mutableLiveStates = MutableLiveData<Map<String, ChangeMembershipState>>(emptyMap())
|
||||
private val states = HashMap<String, ChangeMembershipState>()
|
||||
|
||||
/**
|
||||
* This will update local states to be synced with the server.
|
||||
*/
|
||||
fun setMembershipFromSync(roomId: String, membership: Membership) {
|
||||
if (states.containsKey(roomId)) {
|
||||
val newState = membership.toMembershipChangeState()
|
||||
updateState(roomId, newState)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateState(roomId: String, state: ChangeMembershipState) {
|
||||
states[roomId] = state
|
||||
mutableLiveStates.postValue(states.toMap())
|
||||
}
|
||||
|
||||
fun getLiveStates(): LiveData<Map<String, ChangeMembershipState>> {
|
||||
return mutableLiveStates
|
||||
}
|
||||
|
||||
fun getState(roomId: String): ChangeMembershipState {
|
||||
return states.getOrElse(roomId) {
|
||||
ChangeMembershipState.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun Membership.toMembershipChangeState(): ChangeMembershipState {
|
||||
return when {
|
||||
this == Membership.JOIN -> ChangeMembershipState.Joined
|
||||
this.isLeft() -> ChangeMembershipState.Left
|
||||
else -> ChangeMembershipState.Unknown
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,8 +30,15 @@ internal class RoomMemberEventHandler @Inject constructor() {
|
|||
if (event.type != EventType.STATE_ROOM_MEMBER) {
|
||||
return false
|
||||
}
|
||||
val roomMember = event.content.toModel<RoomMemberContent>() ?: return false
|
||||
val userId = event.stateKey ?: return false
|
||||
val roomMember = event.content.toModel<RoomMemberContent>()
|
||||
return handle(realm, roomId, userId, roomMember)
|
||||
}
|
||||
|
||||
fun handle(realm: Realm, roomId: String, userId: String, roomMember: RoomMemberContent?): Boolean {
|
||||
if (roomMember == null) {
|
||||
return false
|
||||
}
|
||||
val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember)
|
||||
realm.insertOrUpdate(roomMemberEntity)
|
||||
if (roomMember.membership.isActive()) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.internal.session.room.membership.joining
|
||||
|
||||
import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse
|
||||
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
|
@ -24,6 +25,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
|||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import io.realm.RealmConfiguration
|
||||
|
@ -45,12 +47,19 @@ internal class DefaultJoinRoomTask @Inject constructor(
|
|||
private val readMarkersTask: SetReadMarkersTask,
|
||||
@SessionDatabase
|
||||
private val realmConfiguration: RealmConfiguration,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
private val eventBus: EventBus
|
||||
) : JoinRoomTask {
|
||||
|
||||
override suspend fun execute(params: JoinRoomTask.Params) {
|
||||
val joinRoomResponse = executeRequest<JoinRoomResponse>(eventBus) {
|
||||
apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason))
|
||||
roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
|
||||
val joinRoomResponse = try {
|
||||
executeRequest<JoinRoomResponse>(eventBus) {
|
||||
apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.FailedJoining(failure))
|
||||
throw failure
|
||||
}
|
||||
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
||||
val roomId = joinRoomResponse.roomId
|
||||
|
|
|
@ -16,10 +16,19 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.room.membership.leaving
|
||||
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import im.vector.matrix.android.internal.session.room.state.StateEventDataSource
|
||||
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryDataSource
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
|
||||
|
@ -31,12 +40,40 @@ internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
|
|||
|
||||
internal class DefaultLeaveRoomTask @Inject constructor(
|
||||
private val roomAPI: RoomAPI,
|
||||
private val eventBus: EventBus
|
||||
private val eventBus: EventBus,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
|
||||
) : LeaveRoomTask {
|
||||
|
||||
override suspend fun execute(params: LeaveRoomTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason))
|
||||
leaveRoom(params.roomId, params.reason)
|
||||
}
|
||||
|
||||
private suspend fun leaveRoom(roomId: String, reason: String?) {
|
||||
val roomSummary = roomSummaryDataSource.getRoomSummary(roomId)
|
||||
if (roomSummary?.membership?.isActive() == false) {
|
||||
Timber.v("Room $roomId is not joined so can't be left")
|
||||
return
|
||||
}
|
||||
roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.Leaving)
|
||||
val roomCreateStateEvent = stateEventDataSource.getStateEvent(
|
||||
roomId = roomId,
|
||||
eventType = EventType.STATE_ROOM_CREATE,
|
||||
stateKey = QueryStringValue.NoCondition
|
||||
)
|
||||
// Server is not cleaning predecessor rooms, so we also try to left them
|
||||
val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel<RoomCreateContent>()?.predecessor?.roomId
|
||||
if (predecessorRoomId != null) {
|
||||
leaveRoom(predecessorRoomId, reason)
|
||||
}
|
||||
try {
|
||||
executeRequest<Unit>(eventBus) {
|
||||
apiCall = roomAPI.leave(roomId, mapOf("reason" to reason))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.FailedLeaving(failure))
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
|
||||
private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
|
||||
val query = RoomSummaryEntity.where(realm)
|
||||
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
|
||||
query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
|
|
|
@ -349,7 +349,7 @@ internal class DefaultTimeline(
|
|||
|
||||
updateState(Timeline.Direction.FORWARDS) {
|
||||
it.copy(
|
||||
hasMoreInCache = firstBuiltEvent == null || firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE,
|
||||
hasMoreInCache = firstBuiltEvent != null && firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE,
|
||||
hasReachedEnd = chunkEntity?.isLastForward ?: false
|
||||
)
|
||||
}
|
||||
|
@ -369,6 +369,9 @@ internal class DefaultTimeline(
|
|||
private fun paginateInternal(startDisplayIndex: Int?,
|
||||
direction: Timeline.Direction,
|
||||
count: Int): Boolean {
|
||||
if (count == 0) {
|
||||
return false
|
||||
}
|
||||
updateState(direction) { it.copy(requestedPaginationCount = count, isPaginating = true) }
|
||||
val builtCount = buildTimelineEvents(startDisplayIndex, direction, count.toLong())
|
||||
val shouldFetchMore = builtCount < count && !hasReachedEnd(direction)
|
||||
|
|
|
@ -241,12 +241,13 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
|||
chunksToDelete.add(it)
|
||||
}
|
||||
}
|
||||
val shouldUpdateSummary = chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS
|
||||
chunksToDelete.forEach {
|
||||
it.deleteOnCascade()
|
||||
}
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null
|
||||
|| (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS)
|
||||
if (shouldUpdateSummary) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(
|
||||
realm,
|
||||
roomId,
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResu
|
|||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||
import im.vector.matrix.android.internal.database.helper.addTimelineEvent
|
||||
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
|
@ -48,6 +48,7 @@ import im.vector.matrix.android.internal.di.MoshiProvider
|
|||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
||||
import im.vector.matrix.android.internal.session.mapWithProgress
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberEventHandler
|
||||
import im.vector.matrix.android.internal.session.room.read.FullyReadContent
|
||||
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater
|
||||
|
@ -73,6 +74,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
private val cryptoService: DefaultCryptoService,
|
||||
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||
private val roomTypingUsersHandler: RoomTypingUsersHandler,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus,
|
||||
private val timelineEventDecryptor: TimelineEventDecryptor) {
|
||||
|
@ -185,6 +187,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
} != null
|
||||
|
||||
roomTypingUsersHandler.handle(realm, roomId, ephemeralResult)
|
||||
roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.JOIN)
|
||||
roomSummaryUpdater.update(
|
||||
realm,
|
||||
roomId,
|
||||
|
@ -221,6 +224,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
val inviterEvent = roomSync.inviteState?.events?.lastOrNull {
|
||||
it.type == EventType.STATE_ROOM_MEMBER
|
||||
}
|
||||
roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.INVITE)
|
||||
roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = true, inviterId = inviterEvent?.senderId)
|
||||
return roomEntity
|
||||
}
|
||||
|
@ -263,6 +267,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
val membership = leftMember?.membership ?: Membership.LEAVE
|
||||
roomEntity.membership = membership
|
||||
roomEntity.chunks.deleteAllFromRealm()
|
||||
roomTypingUsersHandler.handle(realm, roomId, null)
|
||||
roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE)
|
||||
roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications)
|
||||
return roomEntity
|
||||
}
|
||||
|
@ -307,14 +313,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
root = eventEntity
|
||||
}
|
||||
if (event.type == EventType.STATE_ROOM_MEMBER) {
|
||||
roomMemberContentsByUser[event.stateKey] = event.content.toModel()
|
||||
roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
|
||||
val fixedContent = event.getFixedRoomMemberContent()
|
||||
roomMemberContentsByUser[event.stateKey] = fixedContent
|
||||
roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent)
|
||||
}
|
||||
}
|
||||
roomMemberContentsByUser.getOrPut(event.senderId) {
|
||||
// If we don't have any new state on this user, get it from db
|
||||
val rootStateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root
|
||||
ContentMapper.map(rootStateEvent?.content).toModel()
|
||||
rootStateEvent?.asDomain()?.getFixedRoomMemberContent()
|
||||
}
|
||||
|
||||
chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser)
|
||||
|
@ -405,4 +412,18 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Event.getFixedRoomMemberContent(): RoomMemberContent? {
|
||||
val content = content.toModel<RoomMemberContent>()
|
||||
// if user is leaving, we should grab his last name and avatar from prevContent
|
||||
return if (content?.membership?.isLeft() == true) {
|
||||
val prevContent = resolvedPrevContent().toModel<RoomMemberContent>()
|
||||
content.copy(
|
||||
displayName = prevContent?.displayName,
|
||||
avatarUrl = prevContent?.avatarUrl
|
||||
)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -290,7 +290,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7'
|
||||
// Keep at 2.0.0-beta4 at the moment, as updating is breaking some UI
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
|
||||
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
|
||||
|
|
|
@ -23,21 +23,21 @@ import androidx.fragment.app.FragmentTransaction
|
|||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
|
||||
fun VectorBaseActivity.addFragment(frameId: Int, fragment: Fragment) {
|
||||
supportFragmentManager.commitTransactionNow { add(frameId, fragment) }
|
||||
supportFragmentManager.commitTransaction { add(frameId, fragment) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseActivity.addFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
supportFragmentManager.commitTransactionNow {
|
||||
supportFragmentManager.commitTransaction {
|
||||
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseActivity.replaceFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
supportFragmentManager.commitTransactionNow { replace(frameId, fragment, tag) }
|
||||
supportFragmentManager.commitTransaction { replace(frameId, fragment, tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseActivity.replaceFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
supportFragmentManager.commitTransactionNow {
|
||||
supportFragmentManager.commitTransaction {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,21 +27,21 @@ import java.util.Date
|
|||
import java.util.Locale
|
||||
|
||||
fun VectorBaseFragment.addFragment(frameId: Int, fragment: Fragment) {
|
||||
parentFragmentManager.commitTransactionNow { add(frameId, fragment) }
|
||||
parentFragmentManager.commitTransaction { add(frameId, fragment) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.addFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
parentFragmentManager.commitTransactionNow {
|
||||
parentFragmentManager.commitTransaction {
|
||||
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.replaceFragment(frameId: Int, fragment: Fragment) {
|
||||
parentFragmentManager.commitTransactionNow { replace(frameId, fragment) }
|
||||
parentFragmentManager.commitTransaction { replace(frameId, fragment) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.replaceFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
parentFragmentManager.commitTransactionNow {
|
||||
parentFragmentManager.commitTransaction {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
@ -57,21 +57,21 @@ fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(frameId: Int, fragm
|
|||
}
|
||||
|
||||
fun VectorBaseFragment.addChildFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
childFragmentManager.commitTransactionNow { add(frameId, fragment, tag) }
|
||||
childFragmentManager.commitTransaction { add(frameId, fragment, tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.addChildFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
childFragmentManager.commitTransactionNow {
|
||||
childFragmentManager.commitTransaction {
|
||||
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.replaceChildFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
childFragmentManager.commitTransactionNow { replace(frameId, fragment, tag) }
|
||||
childFragmentManager.commitTransaction { replace(frameId, fragment, tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.replaceChildFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
childFragmentManager.commitTransactionNow {
|
||||
childFragmentManager.commitTransaction {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,421 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Micah Hainline
|
||||
* Copyright (C) 2012 Triposo
|
||||
* Copyright (C) 2013 Paul Imhoff
|
||||
* Copyright (C) 2014 Shahin Yousefi
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package im.vector.riotx.core.platform
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.text.Layout
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextUtils.TruncateAt
|
||||
import android.text.TextUtils.concat
|
||||
import android.text.TextUtils.copySpansFrom
|
||||
import android.text.TextUtils.indexOf
|
||||
import android.text.TextUtils.lastIndexOf
|
||||
import android.text.TextUtils.substring
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import timber.log.Timber
|
||||
import java.util.ArrayList
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/*
|
||||
* Imported from https://gist.github.com/hateum/d2095575b441007d62b8
|
||||
*
|
||||
* Use it in your layout to avoid this issue: https://issuetracker.google.com/issues/121092510
|
||||
*/
|
||||
|
||||
/**
|
||||
* A [android.widget.TextView] that ellipsizes more intelligently.
|
||||
* This class supports ellipsizing multiline text through setting `android:ellipsize`
|
||||
* and `android:maxLines`.
|
||||
*
|
||||
*
|
||||
* Note: [TruncateAt.MARQUEE] ellipsizing type is not supported.
|
||||
* This as to be used to get rid of the StaticLayout issue with maxLines and ellipsize causing some performance issues.
|
||||
*/
|
||||
class EllipsizingTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = android.R.attr.textViewStyle)
|
||||
: AppCompatTextView(context, attrs, defStyle) {
|
||||
|
||||
private val ELLIPSIS = SpannableString("\u2026")
|
||||
private val ellipsizeListeners: MutableList<EllipsizeListener> = ArrayList()
|
||||
private var ellipsizeStrategy: EllipsizeStrategy? = null
|
||||
var isEllipsized = false
|
||||
private set
|
||||
private var isStale = false
|
||||
private var programmaticChange = false
|
||||
private var fullText: CharSequence? = null
|
||||
private var maxLines = 0
|
||||
private var lineSpacingMult = 1.0f
|
||||
private var lineAddVertPad = 0.0f
|
||||
|
||||
/**
|
||||
* The end punctuation which will be removed when appending [.ELLIPSIS].
|
||||
*/
|
||||
private var mEndPunctPattern: Pattern? = null
|
||||
|
||||
fun setEndPunctuationPattern(pattern: Pattern?) {
|
||||
mEndPunctPattern = pattern
|
||||
}
|
||||
|
||||
fun addEllipsizeListener(listener: EllipsizeListener) {
|
||||
ellipsizeListeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeEllipsizeListener(listener: EllipsizeListener) {
|
||||
ellipsizeListeners.remove(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The maximum number of lines displayed in this [android.widget.TextView].
|
||||
*/
|
||||
override fun getMaxLines(): Int {
|
||||
return maxLines
|
||||
}
|
||||
|
||||
override fun setMaxLines(maxLines: Int) {
|
||||
super.setMaxLines(maxLines)
|
||||
this.maxLines = maxLines
|
||||
isStale = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the last fully visible line is being ellipsized.
|
||||
*
|
||||
* @return `true` if the last fully visible line is being ellipsized;
|
||||
* otherwise, returns `false`.
|
||||
*/
|
||||
fun ellipsizingLastFullyVisibleLine(): Boolean {
|
||||
return maxLines == Int.MAX_VALUE
|
||||
}
|
||||
|
||||
override fun setLineSpacing(add: Float, mult: Float) {
|
||||
lineAddVertPad = add
|
||||
lineSpacingMult = mult
|
||||
super.setLineSpacing(add, mult)
|
||||
}
|
||||
|
||||
override fun setText(text: CharSequence?, type: BufferType) {
|
||||
if (!programmaticChange) {
|
||||
fullText = if (text is Spanned) text else text
|
||||
isStale = true
|
||||
}
|
||||
super.setText(text, type)
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
if (ellipsizingLastFullyVisibleLine()) {
|
||||
isStale = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.setPadding(left, top, right, bottom)
|
||||
if (ellipsizingLastFullyVisibleLine()) {
|
||||
isStale = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (isStale) {
|
||||
resetText()
|
||||
}
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ellipsized text if appropriate.
|
||||
*/
|
||||
private fun resetText() {
|
||||
val maxLines = maxLines
|
||||
var workingText = fullText
|
||||
var ellipsized = false
|
||||
if (maxLines != -1) {
|
||||
if (ellipsizeStrategy == null) setEllipsize(null)
|
||||
workingText = ellipsizeStrategy!!.processText(fullText)
|
||||
ellipsized = !ellipsizeStrategy!!.isInLayout(fullText)
|
||||
}
|
||||
if (workingText != text) {
|
||||
programmaticChange = true
|
||||
text = try {
|
||||
workingText
|
||||
} finally {
|
||||
programmaticChange = false
|
||||
}
|
||||
}
|
||||
isStale = false
|
||||
if (ellipsized != isEllipsized) {
|
||||
isEllipsized = ellipsized
|
||||
for (listener in ellipsizeListeners) {
|
||||
listener.ellipsizeStateChanged(ellipsized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes words in the text that are longer than the view is wide to be ellipsized
|
||||
* instead of broken in the middle. Use `null` to turn off ellipsizing.
|
||||
*
|
||||
*
|
||||
* Note: Method does nothing for [TruncateAt.MARQUEE]
|
||||
* ellipsizing type.
|
||||
*
|
||||
* @param where part of text to ellipsize
|
||||
*/
|
||||
override fun setEllipsize(where: TruncateAt?) {
|
||||
if (where == null) {
|
||||
ellipsizeStrategy = EllipsizeNoneStrategy()
|
||||
return
|
||||
}
|
||||
ellipsizeStrategy = when (where) {
|
||||
TruncateAt.END -> EllipsizeEndStrategy()
|
||||
TruncateAt.START -> EllipsizeStartStrategy()
|
||||
TruncateAt.MIDDLE -> EllipsizeMiddleStrategy()
|
||||
TruncateAt.MARQUEE -> EllipsizeNoneStrategy()
|
||||
else -> EllipsizeNoneStrategy()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener that notifies when the ellipsize state has changed.
|
||||
*/
|
||||
interface EllipsizeListener {
|
||||
fun ellipsizeStateChanged(ellipsized: Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for an ellipsize strategy.
|
||||
*/
|
||||
private abstract inner class EllipsizeStrategy {
|
||||
/**
|
||||
* Returns ellipsized text if the text does not fit inside of the layout;
|
||||
* otherwise, returns the full text.
|
||||
*
|
||||
* @param text text to process
|
||||
* @return Ellipsized text if the text does not fit inside of the layout;
|
||||
* otherwise, returns the full text.
|
||||
*/
|
||||
fun processText(text: CharSequence?): CharSequence? {
|
||||
return if (!isInLayout(text)) createEllipsizedText(text) else text
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the text fits inside of the layout.
|
||||
*
|
||||
* @param text text to fit
|
||||
* @return `true` if the text fits inside of the layout;
|
||||
* otherwise, returns `false`.
|
||||
*/
|
||||
fun isInLayout(text: CharSequence?): Boolean {
|
||||
val layout = createWorkingLayout(text)
|
||||
return layout.lineCount <= linesCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a working layout with the given text.
|
||||
*
|
||||
* @param workingText text to create layout with
|
||||
* @return [android.text.Layout] with the given text.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
protected fun createWorkingLayout(workingText: CharSequence?): Layout {
|
||||
return StaticLayout(
|
||||
workingText ?: "",
|
||||
paint,
|
||||
width - compoundPaddingLeft - compoundPaddingRight,
|
||||
Layout.Alignment.ALIGN_NORMAL,
|
||||
lineSpacingMult,
|
||||
lineAddVertPad,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get how many lines of text we are allowed to display.
|
||||
*/
|
||||
protected val linesCount: Int
|
||||
get() = if (ellipsizingLastFullyVisibleLine()) {
|
||||
val fullyVisibleLinesCount = fullyVisibleLinesCount
|
||||
if (fullyVisibleLinesCount == -1) 1 else fullyVisibleLinesCount
|
||||
} else {
|
||||
maxLines
|
||||
}
|
||||
|
||||
/**
|
||||
* Get how many lines of text we can display so their full height is visible.
|
||||
*/
|
||||
protected val fullyVisibleLinesCount: Int
|
||||
get() {
|
||||
val layout = createWorkingLayout("")
|
||||
val height = height - compoundPaddingTop - compoundPaddingBottom
|
||||
val lineHeight = layout.getLineBottom(0)
|
||||
return height / lineHeight
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ellipsized text from the given text.
|
||||
*
|
||||
* @param fullText text to ellipsize
|
||||
* @return Ellipsized text
|
||||
*/
|
||||
protected abstract fun createEllipsizedText(fullText: CharSequence?): CharSequence?
|
||||
}
|
||||
|
||||
/**
|
||||
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||
* does not ellipsize text.
|
||||
*/
|
||||
private inner class EllipsizeNoneStrategy : EllipsizeStrategy() {
|
||||
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||
return fullText
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||
* ellipsizes text at the end.
|
||||
*/
|
||||
private inner class EllipsizeEndStrategy : EllipsizeStrategy() {
|
||||
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||
val layout = createWorkingLayout(fullText)
|
||||
val cutOffIndex = try {
|
||||
layout.getLineEnd(maxLines - 1)
|
||||
} catch (exception: IndexOutOfBoundsException) {
|
||||
// Not sure to understand why this is happening
|
||||
Timber.e(exception, "IndexOutOfBoundsException, maxLine: $maxLines")
|
||||
0
|
||||
}
|
||||
val textLength = fullText!!.length
|
||||
var cutOffLength = textLength - cutOffIndex
|
||||
if (cutOffLength < ELLIPSIS.length) cutOffLength = ELLIPSIS.length
|
||||
var workingText: CharSequence = substring(fullText, 0, textLength - cutOffLength).trim()
|
||||
while (!isInLayout(concat(stripEndPunctuation(workingText), ELLIPSIS))) {
|
||||
val lastSpace = lastIndexOf(workingText, ' ')
|
||||
if (lastSpace == -1) {
|
||||
break
|
||||
}
|
||||
workingText = substring(workingText, 0, lastSpace).trim()
|
||||
}
|
||||
workingText = concat(stripEndPunctuation(workingText), ELLIPSIS)
|
||||
val dest = SpannableStringBuilder(workingText)
|
||||
if (fullText is Spanned) {
|
||||
copySpansFrom(fullText as Spanned?, 0, workingText.length, null, dest, 0)
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the end punctuation from a given text according to [.mEndPunctPattern].
|
||||
*
|
||||
* @param workingText text to strip end punctuation from
|
||||
* @return Text without end punctuation.
|
||||
*/
|
||||
fun stripEndPunctuation(workingText: CharSequence): String {
|
||||
return mEndPunctPattern!!.matcher(workingText).replaceFirst("")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||
* ellipsizes text at the start.
|
||||
*/
|
||||
private inner class EllipsizeStartStrategy : EllipsizeStrategy() {
|
||||
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||
val layout = createWorkingLayout(fullText)
|
||||
val cutOffIndex = layout.getLineEnd(maxLines - 1)
|
||||
val textLength = fullText!!.length
|
||||
var cutOffLength = textLength - cutOffIndex
|
||||
if (cutOffLength < ELLIPSIS.length) cutOffLength = ELLIPSIS.length
|
||||
var workingText: CharSequence = substring(fullText, cutOffLength, textLength).trim()
|
||||
while (!isInLayout(concat(ELLIPSIS, workingText))) {
|
||||
val firstSpace = indexOf(workingText, ' ')
|
||||
if (firstSpace == -1) {
|
||||
break
|
||||
}
|
||||
workingText = substring(workingText, firstSpace, workingText.length).trim()
|
||||
}
|
||||
workingText = concat(ELLIPSIS, workingText)
|
||||
val dest = SpannableStringBuilder(workingText)
|
||||
if (fullText is Spanned) {
|
||||
copySpansFrom(fullText as Spanned?, textLength - workingText.length,
|
||||
textLength, null, dest, 0)
|
||||
}
|
||||
return dest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||
* ellipsizes text in the middle.
|
||||
*/
|
||||
private inner class EllipsizeMiddleStrategy : EllipsizeStrategy() {
|
||||
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||
val layout = createWorkingLayout(fullText)
|
||||
val cutOffIndex = layout.getLineEnd(maxLines - 1)
|
||||
val textLength = fullText!!.length
|
||||
var cutOffLength = textLength - cutOffIndex
|
||||
if (cutOffLength < ELLIPSIS.length) cutOffLength = ELLIPSIS.length
|
||||
cutOffLength += cutOffIndex % 2 // Make it even.
|
||||
var firstPart = substring(
|
||||
fullText, 0, textLength / 2 - cutOffLength / 2).trim()
|
||||
var secondPart = substring(
|
||||
fullText, textLength / 2 + cutOffLength / 2, textLength).trim()
|
||||
while (!isInLayout(concat(firstPart, ELLIPSIS, secondPart))) {
|
||||
val lastSpaceFirstPart = firstPart.lastIndexOf(' ')
|
||||
val firstSpaceSecondPart = secondPart.indexOf(' ')
|
||||
if (lastSpaceFirstPart == -1 || firstSpaceSecondPart == -1) break
|
||||
firstPart = firstPart.substring(0, lastSpaceFirstPart).trim()
|
||||
secondPart = secondPart.substring(firstSpaceSecondPart, secondPart.length).trim()
|
||||
}
|
||||
val firstDest = SpannableStringBuilder(firstPart)
|
||||
val secondDest = SpannableStringBuilder(secondPart)
|
||||
if (fullText is Spanned) {
|
||||
copySpansFrom(fullText as Spanned?, 0, firstPart.length,
|
||||
null, firstDest, 0)
|
||||
copySpansFrom(fullText as Spanned?, textLength - secondPart.length,
|
||||
textLength, null, secondDest, 0)
|
||||
}
|
||||
return concat(firstDest, ELLIPSIS, secondDest)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ELLIPSIZE_ALPHA = 0x88
|
||||
private val DEFAULT_END_PUNCTUATION = Pattern.compile("[.!?,;:\u2026]*$", Pattern.DOTALL)
|
||||
}
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs, intArrayOf(android.R.attr.maxLines, android.R.attr.ellipsize), defStyle) {
|
||||
maxLines = getInt(0, Int.MAX_VALUE)
|
||||
}
|
||||
setEndPunctuationPattern(DEFAULT_END_PUNCTUATION)
|
||||
val currentTextColor = currentTextColor
|
||||
val ellipsizeColor = Color.argb(ELLIPSIZE_ALPHA, Color.red(currentTextColor), Color.green(currentTextColor), Color.blue(currentTextColor))
|
||||
ELLIPSIS.setSpan(ForegroundColorSpan(ellipsizeColor), 0, ELLIPSIS.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
|
@ -19,13 +19,11 @@ package im.vector.riotx.core.ui.views
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import butterknife.OnClick
|
||||
|
@ -80,8 +78,6 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
state = newState
|
||||
|
||||
hideAll()
|
||||
val parent = parent as ViewGroup
|
||||
TransitionManager.beginDelayedTransition(parent)
|
||||
when (newState) {
|
||||
State.Initial -> renderInitial()
|
||||
State.Hidden -> renderHidden()
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
|
|||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.commitTransactionNow
|
||||
import im.vector.riotx.core.extensions.commitTransaction
|
||||
import im.vector.riotx.core.glide.GlideApp
|
||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
|
@ -254,7 +254,7 @@ class HomeDetailFragment @Inject constructor(
|
|||
private fun updateSelectedFragment(displayMode: RoomListDisplayMode) {
|
||||
val fragmentTag = "FRAGMENT_TAG_${displayMode.name}"
|
||||
val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag)
|
||||
childFragmentManager.commitTransactionNow {
|
||||
childFragmentManager.commitTransaction {
|
||||
childFragmentManager.fragments
|
||||
.filter { it != fragmentToShow }
|
||||
.forEach {
|
||||
|
|
|
@ -20,7 +20,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import im.vector.riotx.core.utils.Debouncer
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Show or hide the jumpToBottomView, depending on the scrolling and if the timeline is displaying the more recent event
|
||||
|
@ -67,7 +66,6 @@ class JumpToBottomViewVisibilityManager(
|
|||
}
|
||||
|
||||
private fun maybeShowJumpToBottomViewVisibility() {
|
||||
Timber.v("First visible: ${layoutManager.findFirstCompletelyVisibleItemPosition()}")
|
||||
if (layoutManager.findFirstVisibleItemPosition() != 0) {
|
||||
jumpToBottomView.show()
|
||||
} else {
|
||||
|
|
|
@ -69,6 +69,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||
|
@ -636,7 +637,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
|
||||
formattedBody = eventHtmlRenderer.render(document)
|
||||
}
|
||||
composerLayout.composerRelatedMessageContent.text = formattedBody ?: nonFormattedBody
|
||||
composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
|
||||
|
||||
updateComposerText(defaultContent)
|
||||
|
||||
|
@ -853,12 +854,14 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun invalidate() = withState(roomDetailViewModel) { state ->
|
||||
renderRoomSummary(state)
|
||||
invalidateOptionsMenu()
|
||||
val summary = state.asyncRoomSummary()
|
||||
renderToolbar(summary, state.typingMessage)
|
||||
val inviter = state.asyncInviter()
|
||||
if (summary?.membership == Membership.JOIN) {
|
||||
roomWidgetsBannerView.render(state.activeRoomWidgets())
|
||||
jumpToBottomView.count = summary.notificationCount
|
||||
jumpToBottomView.drawBadge = summary.hasUnreadMessages
|
||||
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
||||
timelineEventController.update(state)
|
||||
inviteView.visibility = View.GONE
|
||||
|
@ -880,7 +883,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
||||
inviteView.visibility = View.VISIBLE
|
||||
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
|
||||
inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState)
|
||||
// Intercept click event
|
||||
inviteView.setOnClickListener { }
|
||||
} else if (state.asyncInviter.complete) {
|
||||
|
@ -888,15 +891,15 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun renderRoomSummary(state: RoomDetailViewState) {
|
||||
state.asyncRoomSummary()?.let { roomSummary ->
|
||||
private fun renderToolbar(roomSummary: RoomSummary?, typingMessage: String?) {
|
||||
if (roomSummary == null) {
|
||||
roomToolbarContentView.isClickable = false
|
||||
} else {
|
||||
roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN
|
||||
roomToolbarTitleView.text = roomSummary.displayName
|
||||
avatarRenderer.render(roomSummary.toMatrixItem(), roomToolbarAvatarImageView)
|
||||
|
||||
renderSubTitle(state.typingMessage, roomSummary.topic)
|
||||
jumpToBottomView.count = roomSummary.notificationCount
|
||||
jumpToBottomView.drawBadge = roomSummary.hasUnreadMessages
|
||||
|
||||
renderSubTitle(typingMessage, roomSummary.topic)
|
||||
roomToolbarDecorationImageView.let {
|
||||
it.setImageResource(roomSummary.roomEncryptionTrustLevel.toImageRes())
|
||||
it.isVisible = roomSummary.roomEncryptionTrustLevel != null
|
||||
|
|
|
@ -40,6 +40,7 @@ import im.vector.matrix.android.api.session.events.model.toContent
|
|||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
|
@ -166,6 +167,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
timeline.start()
|
||||
timeline.addListener(this)
|
||||
observeRoomSummary()
|
||||
observeMembershipChanges()
|
||||
observeSummaryState()
|
||||
getUnreadState()
|
||||
observeSyncState()
|
||||
|
@ -405,17 +407,22 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
|
||||
private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled()
|
||||
|
||||
fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) {
|
||||
R.id.clear_message_queue ->
|
||||
// For now always disable when not in developer mode, worker cancellation is not working properly
|
||||
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
||||
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
|
||||
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
|
||||
R.id.open_matrix_apps -> true
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> room.canStartCall() && webRtcPeerConnectionManager.currentCall == null
|
||||
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
||||
else -> false
|
||||
fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state ->
|
||||
if (state.asyncRoomSummary()?.membership != Membership.JOIN) {
|
||||
return@withState false
|
||||
}
|
||||
when (itemId) {
|
||||
R.id.clear_message_queue ->
|
||||
// For now always disable when not in developer mode, worker cancellation is not working properly
|
||||
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
||||
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
|
||||
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
|
||||
R.id.open_matrix_apps -> true
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> room.canStartCall() && webRtcPeerConnectionManager.currentCall == null
|
||||
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
@ -624,7 +631,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
|
||||
session.joinRoom(command.roomAlias, command.reason, object : MatrixCallback<Unit> {
|
||||
session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
session.getRoomSummary(command.roomAlias)
|
||||
?.roomId
|
||||
|
@ -846,17 +853,14 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) {
|
||||
setState { copy(sendMode = SendMode.REGULAR(action.text)) }
|
||||
withState { state ->
|
||||
// For edit, just delete the current draft
|
||||
if (state.sendMode is SendMode.EDIT) {
|
||||
room.deleteDraft(NoOpMatrixCallback())
|
||||
} else {
|
||||
// Save a new draft and keep the previously entered text
|
||||
room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback())
|
||||
}
|
||||
private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState {
|
||||
if (it.sendMode is SendMode.EDIT) {
|
||||
room.deleteDraft(NoOpMatrixCallback())
|
||||
} else {
|
||||
// Save a new draft and keep the previously entered text
|
||||
room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback())
|
||||
}
|
||||
setState { copy(sendMode = SendMode.REGULAR(action.text)) }
|
||||
}
|
||||
|
||||
private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
|
||||
|
@ -1145,6 +1149,19 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun observeMembershipChanges() {
|
||||
session.rx()
|
||||
.liveRoomChangeMembershipState()
|
||||
.map {
|
||||
it[initialState.roomId] ?: ChangeMembershipState.Unknown
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
setState { copy(changeMembershipState = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeSummaryState() {
|
||||
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
|
||||
roomSummaryHolder.set(summary)
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.airbnb.mvrx.Async
|
|||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
|
@ -64,6 +65,7 @@ data class RoomDetailViewState(
|
|||
val highlightedEventId: String? = null,
|
||||
val unreadState: UnreadState = UnreadState.Unknown,
|
||||
val canShowJumpToReadMarker: Boolean = true,
|
||||
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
|
||||
val canSendMessage: Boolean = true
|
||||
) : MvRxState {
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
|
|||
|
||||
private fun formatRoomPowerLevels(event: Event, disambiguatedDisplayName: String): CharSequence? {
|
||||
val powerLevelsContent: PowerLevelsContent = event.getClearContent().toModel() ?: return null
|
||||
val previousPowerLevelsContent: PowerLevelsContent = event.prevContent.toModel() ?: return null
|
||||
val previousPowerLevelsContent: PowerLevelsContent = event.resolvedPrevContent().toModel() ?: return null
|
||||
val userIds = HashSet<String>()
|
||||
userIds.addAll(powerLevelsContent.users.keys)
|
||||
userIds.addAll(previousPowerLevelsContent.users.keys)
|
||||
|
@ -123,7 +123,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
|
|||
|
||||
private fun formatWidgetEvent(event: Event, disambiguatedDisplayName: String): CharSequence? {
|
||||
val widgetContent: WidgetContent = event.getClearContent().toModel() ?: return null
|
||||
val previousWidgetContent: WidgetContent? = event.prevContent.toModel()
|
||||
val previousWidgetContent: WidgetContent? = event.resolvedPrevContent().toModel()
|
||||
return if (widgetContent.isActive()) {
|
||||
val widgetName = widgetContent.getHumanName()
|
||||
if (previousWidgetContent?.isActive().orFalse()) {
|
||||
|
@ -297,7 +297,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
|
|||
|
||||
private fun formatRoomMemberEvent(event: Event, senderName: String?): String? {
|
||||
val eventContent: RoomMemberContent? = event.getClearContent().toModel()
|
||||
val prevEventContent: RoomMemberContent? = event.prevContent.toModel()
|
||||
val prevEventContent: RoomMemberContent? = event.resolvedPrevContent().toModel()
|
||||
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
|
||||
return if (isMembershipEvent) {
|
||||
buildMembershipNotice(event, senderName, eventContent, prevEventContent)
|
||||
|
@ -308,7 +308,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
|
|||
|
||||
private fun formatRoomAliasesEvent(event: Event, senderName: String?): String? {
|
||||
val eventContent: RoomAliasesContent? = event.getClearContent().toModel()
|
||||
val prevEventContent: RoomAliasesContent? = event.unsignedData?.prevContent?.toModel()
|
||||
val prevEventContent: RoomAliasesContent? = event.resolvedPrevContent()?.toModel()
|
||||
|
||||
val addedAliases = eventContent?.aliases.orEmpty() - prevEventContent?.aliases.orEmpty()
|
||||
val removedAliases = prevEventContent?.aliases.orEmpty() - eventContent?.aliases.orEmpty()
|
||||
|
|
|
@ -19,9 +19,9 @@ package im.vector.riotx.features.home.room.list
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
|
@ -29,6 +29,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
|||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.platform.ButtonStateView
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.invite.InviteButtonStateBinder
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_room_invitation)
|
||||
abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>() {
|
||||
|
@ -37,53 +38,36 @@ abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>(
|
|||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var secondLine: CharSequence? = null
|
||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||
@EpoxyAttribute var invitationAcceptInProgress: Boolean = false
|
||||
@EpoxyAttribute var invitationAcceptInError: Boolean = false
|
||||
@EpoxyAttribute var invitationRejectInProgress: Boolean = false
|
||||
@EpoxyAttribute var invitationRejectInError: Boolean = false
|
||||
@EpoxyAttribute lateinit var changeMembershipState: ChangeMembershipState
|
||||
@EpoxyAttribute var acceptListener: (() -> Unit)? = null
|
||||
@EpoxyAttribute var rejectListener: (() -> Unit)? = null
|
||||
|
||||
private val acceptCallback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
acceptListener?.invoke()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
acceptListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
private val rejectCallback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
rejectListener?.invoke()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
rejectListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||
|
||||
// When a request is in progress (accept or reject), we only use the accept State button
|
||||
val requestInProgress = invitationAcceptInProgress || invitationRejectInProgress
|
||||
|
||||
when {
|
||||
requestInProgress -> holder.acceptView.render(ButtonStateView.State.Loading)
|
||||
invitationAcceptInError -> holder.acceptView.render(ButtonStateView.State.Error)
|
||||
else -> holder.acceptView.render(ButtonStateView.State.Button)
|
||||
}
|
||||
// ButtonStateView.State.Loaded not used because roomSummary will not be displayed as a room invitation anymore
|
||||
|
||||
holder.acceptView.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
acceptListener?.invoke()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
acceptListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
holder.rejectView.isVisible = !requestInProgress
|
||||
|
||||
when {
|
||||
invitationRejectInError -> holder.rejectView.render(ButtonStateView.State.Error)
|
||||
else -> holder.rejectView.render(ButtonStateView.State.Button)
|
||||
}
|
||||
|
||||
holder.rejectView.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
rejectListener?.invoke()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
rejectListener?.invoke()
|
||||
}
|
||||
}
|
||||
holder.acceptView.callback = acceptCallback
|
||||
holder.rejectView.callback = rejectCallback
|
||||
InviteButtonStateBinder.bind(holder.acceptView, holder.rejectView, changeMembershipState)
|
||||
holder.titleView.text = matrixItem.getBestName()
|
||||
holder.subtitleView.setTextOrHide(secondLine)
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
|
|
|
@ -21,10 +21,12 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.ViewModelContext
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.DataSource
|
||||
|
@ -55,6 +57,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
observeMembershipChanges()
|
||||
}
|
||||
|
||||
override fun handle(action: RoomListAction) {
|
||||
|
@ -102,37 +105,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
.observeOn(Schedulers.computation())
|
||||
.map { buildRoomSummaries(it) }
|
||||
.execute { async ->
|
||||
val invitedRooms = async()?.get(RoomCategory.INVITE)?.map { it.roomId }.orEmpty()
|
||||
val remainingJoining = joiningRoomsIds.intersect(invitedRooms)
|
||||
val remainingJoinErrors = joiningErrorRoomsIds.intersect(invitedRooms)
|
||||
val remainingRejecting = rejectingRoomsIds.intersect(invitedRooms)
|
||||
val remainingRejectErrors = rejectingErrorRoomsIds.intersect(invitedRooms)
|
||||
copy(
|
||||
asyncFilteredRooms = async,
|
||||
joiningRoomsIds = remainingJoining,
|
||||
joiningErrorRoomsIds = remainingJoinErrors,
|
||||
rejectingRoomsIds = remainingRejecting,
|
||||
rejectingErrorRoomsIds = remainingRejectErrors
|
||||
)
|
||||
copy(asyncFilteredRooms = async)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state ->
|
||||
val roomId = action.roomSummary.roomId
|
||||
|
||||
if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) {
|
||||
val roomMembershipChange = state.roomMembershipChanges[roomId]
|
||||
if (roomMembershipChange?.isInProgress().orFalse()) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to join an already joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds + roomId,
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds - roomId
|
||||
)
|
||||
}
|
||||
|
||||
session.getRoom(roomId)?.join(callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||
|
@ -142,32 +127,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
override fun onFailure(failure: Throwable) {
|
||||
// Notify the user
|
||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds - roomId,
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds + roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleRejectInvitation(action: RoomListAction.RejectInvitation) = withState { state ->
|
||||
val roomId = action.roomSummary.roomId
|
||||
|
||||
if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) {
|
||||
val roomMembershipChange = state.roomMembershipChanges[roomId]
|
||||
if (roomMembershipChange?.isInProgress().orFalse()) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to reject an already rejecting room. Should not happen")
|
||||
Timber.w("Try to left an already leaving or joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
rejectingRoomsIds = rejectingRoomsIds + roomId,
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds - roomId
|
||||
)
|
||||
}
|
||||
|
||||
session.getRoom(roomId)?.leave(null, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data.
|
||||
|
@ -179,12 +151,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
override fun onFailure(failure: Throwable) {
|
||||
// Notify the user
|
||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
rejectingRoomsIds = rejectingRoomsIds - roomId,
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds + roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -235,6 +201,16 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
})
|
||||
}
|
||||
|
||||
private fun observeMembershipChanges() {
|
||||
session.rx()
|
||||
.liveRoomChangeMembershipState()
|
||||
.subscribe {
|
||||
Timber.v("ChangeMembership states: $it")
|
||||
setState { copy(roomMembershipChanges = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||
// Set up init size on directChats and groupRooms as they are the biggest ones
|
||||
val invites = ArrayList<RoomSummary>()
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.annotation.StringRes
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
|
@ -30,14 +31,7 @@ data class RoomListViewState(
|
|||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||
val roomFilter: String = "",
|
||||
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
|
||||
// List of roomIds that the user wants to join
|
||||
val joiningRoomsIds: Set<String> = emptySet(),
|
||||
// List of roomIds that the user wants to join, but an error occurred
|
||||
val joiningErrorRoomsIds: Set<String> = emptySet(),
|
||||
// List of roomIds that the user wants to join
|
||||
val rejectingRoomsIds: Set<String> = emptySet(),
|
||||
// List of roomIds that the user wants to reject, but an error occurred
|
||||
val rejectingErrorRoomsIds: Set<String> = emptySet(),
|
||||
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
|
||||
val isInviteExpanded: Boolean = true,
|
||||
val isFavouriteRoomsExpanded: Boolean = true,
|
||||
val isDirectRoomsExpanded: Boolean = true,
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.list
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
|
@ -72,10 +73,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
|
||||
|
||||
buildRoomModels(filteredSummaries,
|
||||
viewState.joiningRoomsIds,
|
||||
viewState.joiningErrorRoomsIds,
|
||||
viewState.rejectingRoomsIds,
|
||||
viewState.rejectingErrorRoomsIds,
|
||||
viewState.roomMembershipChanges,
|
||||
emptySet())
|
||||
|
||||
addFilterFooter(viewState)
|
||||
|
@ -94,10 +92,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
}
|
||||
if (isExpanded) {
|
||||
buildRoomModels(summaries,
|
||||
viewState.joiningRoomsIds,
|
||||
viewState.joiningErrorRoomsIds,
|
||||
viewState.rejectingRoomsIds,
|
||||
viewState.rejectingErrorRoomsIds,
|
||||
viewState.roomMembershipChanges,
|
||||
emptySet())
|
||||
// Never set showHelp to true for invitation
|
||||
if (category != RoomCategory.INVITE) {
|
||||
|
@ -153,18 +148,12 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
}
|
||||
|
||||
private fun buildRoomModels(summaries: List<RoomSummary>,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
roomChangedMembershipStates: Map<String, ChangeMembershipState>,
|
||||
selectedRoomIds: Set<String>) {
|
||||
summaries.forEach { roomSummary ->
|
||||
roomSummaryItemFactory
|
||||
.create(roomSummary,
|
||||
joiningRoomsIds,
|
||||
joiningErrorRoomsIds,
|
||||
rejectingRoomsIds,
|
||||
rejectingErrorRoomsIds,
|
||||
roomChangedMembershipStates,
|
||||
selectedRoomIds,
|
||||
listener)
|
||||
.addTo(this)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.home.room.list
|
||||
|
||||
import android.view.View
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
|
@ -39,23 +40,20 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
private val avatarRenderer: AvatarRenderer) {
|
||||
|
||||
fun create(roomSummary: RoomSummary,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||
selectedRoomIds: Set<String>,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
return when (roomSummary.membership) {
|
||||
Membership.INVITE -> createInvitationItem(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener)
|
||||
Membership.INVITE -> {
|
||||
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
|
||||
createInvitationItem(roomSummary, changeMembershipState, listener)
|
||||
}
|
||||
else -> createRoomItem(roomSummary, selectedRoomIds, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked })
|
||||
}
|
||||
}
|
||||
|
||||
fun createInvitationItem(roomSummary: RoomSummary,
|
||||
joiningRoomsIds: Set<String>,
|
||||
joiningErrorRoomsIds: Set<String>,
|
||||
rejectingRoomsIds: Set<String>,
|
||||
rejectingErrorRoomsIds: Set<String>,
|
||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||
changeMembershipState: ChangeMembershipState,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
val secondLine = if (roomSummary.isDirect) {
|
||||
roomSummary.inviterId
|
||||
|
@ -70,10 +68,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
.avatarRenderer(avatarRenderer)
|
||||
.matrixItem(roomSummary.toMatrixItem())
|
||||
.secondLine(secondLine)
|
||||
.invitationAcceptInProgress(joiningRoomsIds.contains(roomSummary.roomId))
|
||||
.invitationAcceptInError(joiningErrorRoomsIds.contains(roomSummary.roomId))
|
||||
.invitationRejectInProgress(rejectingRoomsIds.contains(roomSummary.roomId))
|
||||
.invitationRejectInError(rejectingErrorRoomsIds.contains(roomSummary.roomId))
|
||||
.changeMembershipState(changeMembershipState)
|
||||
.acceptListener { listener?.onAcceptRoomInvitation(roomSummary) }
|
||||
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
|
||||
.listener { listener?.onRoomClicked(roomSummary) }
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.invite
|
||||
|
||||
import androidx.core.view.isInvisible
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.riotx.core.platform.ButtonStateView
|
||||
|
||||
object InviteButtonStateBinder {
|
||||
|
||||
fun bind(
|
||||
acceptView: ButtonStateView,
|
||||
rejectView: ButtonStateView,
|
||||
changeMembershipState: ChangeMembershipState
|
||||
) {
|
||||
// When a request is in progress (accept or reject), we only use the accept State button
|
||||
// We check for isSuccessful, otherwise we get a glitch the time room summaries get rebuilt
|
||||
|
||||
val requestInProgress = changeMembershipState.isInProgress() || changeMembershipState.isSuccessful()
|
||||
when {
|
||||
requestInProgress -> acceptView.render(ButtonStateView.State.Loading)
|
||||
changeMembershipState is ChangeMembershipState.FailedJoining -> acceptView.render(ButtonStateView.State.Error)
|
||||
else -> acceptView.render(ButtonStateView.State.Button)
|
||||
}
|
||||
// ButtonStateView.State.Loaded not used because roomSummary will not be displayed as a room invitation anymore
|
||||
|
||||
rejectView.isInvisible = requestInProgress
|
||||
|
||||
when {
|
||||
changeMembershipState is ChangeMembershipState.FailedLeaving -> rejectView.render(ButtonStateView.State.Error)
|
||||
else -> rejectView.render(ButtonStateView.State.Button)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,12 @@ import android.util.AttributeSet
|
|||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.platform.ButtonStateView
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import kotlinx.android.synthetic.main.vector_invite_view.view.*
|
||||
import javax.inject.Inject
|
||||
|
@ -50,11 +52,28 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
|
|||
context.injector().inject(this)
|
||||
}
|
||||
View.inflate(context, R.layout.vector_invite_view, this)
|
||||
inviteRejectView.setOnClickListener { callback?.onRejectInvite() }
|
||||
inviteAcceptView.setOnClickListener { callback?.onAcceptInvite() }
|
||||
inviteAcceptView.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
callback?.onAcceptInvite()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
callback?.onAcceptInvite()
|
||||
}
|
||||
}
|
||||
|
||||
inviteRejectView.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
callback?.onRejectInvite()
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
callback?.onRejectInvite()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun render(sender: User, mode: Mode = Mode.LARGE) {
|
||||
fun render(sender: User, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) {
|
||||
if (mode == Mode.LARGE) {
|
||||
updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT }
|
||||
avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView)
|
||||
|
@ -68,5 +87,6 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
|
|||
inviteNameView.visibility = View.GONE
|
||||
inviteLabelView.text = context.getString(R.string.invited_by, sender.userId)
|
||||
}
|
||||
InviteButtonStateBinder.bind(inviteAcceptView, inviteRejectView, changeMembershipState)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import androidx.core.view.ViewCompat
|
|||
import androidx.fragment.app.Fragment
|
||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
|
@ -178,8 +179,8 @@ class DefaultNavigator @Inject constructor(
|
|||
activity.finish()
|
||||
}
|
||||
|
||||
override fun openRoomPreview(publicRoom: PublicRoom, context: Context) {
|
||||
val intent = RoomPreviewActivity.getIntent(context, publicRoom)
|
||||
override fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData) {
|
||||
val intent = RoomPreviewActivity.getIntent(context, publicRoom, roomDirectoryData)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.view.View
|
|||
import androidx.core.util.Pair
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
|
@ -49,7 +50,7 @@ interface Navigator {
|
|||
|
||||
fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String? = null, buildTask: Boolean = false)
|
||||
|
||||
fun openRoomPreview(publicRoom: PublicRoom, context: Context)
|
||||
fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData)
|
||||
|
||||
fun openCreateRoom(context: Context, initialName: String = "")
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.airbnb.epoxy.VisibilityState
|
|||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import com.airbnb.mvrx.Success
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.riotx.R
|
||||
|
@ -89,13 +90,14 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri
|
|||
roomTopic(publicRoom.topic)
|
||||
nbOfMembers(publicRoom.numJoinedMembers)
|
||||
|
||||
val roomChangeMembership = viewState.changeMembershipStates[publicRoom.roomId] ?: ChangeMembershipState.Unknown
|
||||
val isJoined = viewState.joinedRoomsIds.contains(publicRoom.roomId) || roomChangeMembership is ChangeMembershipState.Joined
|
||||
val joinState = when {
|
||||
viewState.joinedRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINED
|
||||
viewState.joiningRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINING
|
||||
viewState.joiningErrorRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINING_ERROR
|
||||
else -> JoinState.NOT_JOINED
|
||||
isJoined -> JoinState.JOINED
|
||||
roomChangeMembership is ChangeMembershipState.Joining -> JoinState.JOINING
|
||||
roomChangeMembership is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR
|
||||
else -> JoinState.NOT_JOINED
|
||||
}
|
||||
|
||||
joinState(joinState)
|
||||
|
||||
joinListener {
|
||||
|
|
|
@ -114,26 +114,22 @@ class PublicRoomsFragment @Inject constructor(
|
|||
|
||||
override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) {
|
||||
Timber.v("PublicRoomClicked: $publicRoom")
|
||||
|
||||
when (joinState) {
|
||||
JoinState.JOINED -> {
|
||||
navigator.openRoom(requireActivity(), publicRoom.roomId)
|
||||
}
|
||||
JoinState.NOT_JOINED,
|
||||
JoinState.JOINING_ERROR -> {
|
||||
// ROOM PREVIEW
|
||||
navigator.openRoomPreview(publicRoom, requireActivity())
|
||||
}
|
||||
else -> {
|
||||
Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
withState(viewModel) { state ->
|
||||
when (joinState) {
|
||||
JoinState.JOINED -> {
|
||||
navigator.openRoom(requireActivity(), publicRoom.roomId)
|
||||
}
|
||||
else -> {
|
||||
// ROOM PREVIEW
|
||||
navigator.openRoomPreview(requireActivity(), publicRoom, state.roomDirectoryData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPublicRoomJoin(publicRoom: PublicRoom) {
|
||||
Timber.v("PublicRoomJoinClicked: $publicRoom")
|
||||
viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom.getPrimaryAlias(), publicRoom.roomId))
|
||||
viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom.roomId))
|
||||
}
|
||||
|
||||
override fun loadMore() {
|
||||
|
|
|
@ -19,7 +19,9 @@ package im.vector.riotx.features.roomdirectory
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
||||
|
||||
data class PublicRoomsViewState(
|
||||
// The current filter
|
||||
|
@ -30,11 +32,9 @@ data class PublicRoomsViewState(
|
|||
val asyncPublicRoomsRequest: Async<List<PublicRoom>> = Uninitialized,
|
||||
// True if more result are available server side
|
||||
val hasMore: Boolean = false,
|
||||
// Set of roomIds that the user wants to join
|
||||
val joiningRoomsIds: Set<String> = emptySet(),
|
||||
// Set of roomIds that the user wants to join, but an error occurred
|
||||
val joiningErrorRoomsIds: Set<String> = emptySet(),
|
||||
// Set of joined roomId,
|
||||
val joinedRoomsIds: Set<String> = emptySet(),
|
||||
val roomDirectoryDisplayName: String? = null
|
||||
// keys are room alias or roomId
|
||||
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
|
||||
val roomDirectoryData: RoomDirectoryData = RoomDirectoryData()
|
||||
) : MvRxState
|
||||
|
|
|
@ -23,5 +23,5 @@ sealed class RoomDirectoryAction : VectorViewModelAction {
|
|||
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction()
|
||||
data class FilterWith(val filter: String) : RoomDirectoryAction()
|
||||
object LoadMore : RoomDirectoryAction()
|
||||
data class JoinRoom(val roomAlias: String?, val roomId: String) : RoomDirectoryAction()
|
||||
data class JoinRoom(val roomId: String) : RoomDirectoryAction()
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.airbnb.mvrx.appendAt
|
|||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
|
@ -63,18 +64,10 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
|
||||
private var currentTask: Cancelable? = null
|
||||
|
||||
// Default RoomDirectoryData
|
||||
private var roomDirectoryData = RoomDirectoryData()
|
||||
|
||||
init {
|
||||
setState {
|
||||
copy(
|
||||
roomDirectoryDisplayName = roomDirectoryData.displayName
|
||||
)
|
||||
}
|
||||
|
||||
// Observe joined room (from the sync)
|
||||
observeJoinedRooms()
|
||||
observeMembershipChanges()
|
||||
}
|
||||
|
||||
private fun observeJoinedRooms() {
|
||||
|
@ -91,18 +84,21 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
?: emptySet()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joinedRoomsIds = joinedRoomIds,
|
||||
// Remove (newly) joined room id from the joining room list
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) },
|
||||
// Remove (newly) joined room id from the joining room list in error
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) }
|
||||
)
|
||||
copy(joinedRoomsIds = joinedRoomIds)
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeMembershipChanges() {
|
||||
session.rx()
|
||||
.liveRoomChangeMembershipState()
|
||||
.subscribe {
|
||||
setState { copy(changeMembershipStates = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
override fun handle(action: RoomDirectoryAction) {
|
||||
when (action) {
|
||||
is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action)
|
||||
|
@ -112,15 +108,15 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
}
|
||||
}
|
||||
|
||||
private fun setRoomDirectoryData(action: RoomDirectoryAction.SetRoomDirectoryData) {
|
||||
if (this.roomDirectoryData == action.roomDirectoryData) {
|
||||
return
|
||||
private fun setRoomDirectoryData(action: RoomDirectoryAction.SetRoomDirectoryData) = withState {
|
||||
if (it.roomDirectoryData == action.roomDirectoryData) {
|
||||
return@withState
|
||||
}
|
||||
setState {
|
||||
copy(roomDirectoryData = action.roomDirectoryData)
|
||||
}
|
||||
|
||||
this.roomDirectoryData = action.roomDirectoryData
|
||||
|
||||
reset("")
|
||||
load("")
|
||||
load("", action.roomDirectoryData)
|
||||
}
|
||||
|
||||
private fun filterWith(action: RoomDirectoryAction.FilterWith) = withState { state ->
|
||||
|
@ -128,7 +124,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
currentTask?.cancel()
|
||||
|
||||
reset(action.filter)
|
||||
load(action.filter)
|
||||
load(action.filter, state.roomDirectoryData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +137,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
publicRooms = emptyList(),
|
||||
asyncPublicRoomsRequest = Loading(),
|
||||
hasMore = false,
|
||||
roomDirectoryDisplayName = roomDirectoryData.displayName,
|
||||
currentFilter = newFilter
|
||||
)
|
||||
}
|
||||
|
@ -154,12 +149,11 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
asyncPublicRoomsRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
load(state.currentFilter)
|
||||
load(state.currentFilter, state.roomDirectoryData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun load(filter: String) {
|
||||
private fun load(filter: String, roomDirectoryData: RoomDirectoryData) {
|
||||
currentTask = session.getPublicRooms(roomDirectoryData.homeServer,
|
||||
PublicRoomsParams(
|
||||
limit = PUBLIC_ROOMS_LIMIT,
|
||||
|
@ -204,19 +198,16 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
}
|
||||
|
||||
private fun joinRoom(action: RoomDirectoryAction.JoinRoom) = withState { state ->
|
||||
if (state.joiningRoomsIds.contains(action.roomId)) {
|
||||
val roomMembershipChange = state.changeMembershipStates[action.roomId]
|
||||
if (roomMembershipChange?.isInProgress().orFalse()) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to join an already joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(action.roomId) }
|
||||
)
|
||||
}
|
||||
|
||||
session.joinRoom(action.roomAlias ?: action.roomId, callback = object : MatrixCallback<Unit> {
|
||||
val viaServers = state.roomDirectoryData.homeServer?.let {
|
||||
listOf(it)
|
||||
} ?: emptyList()
|
||||
session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||
// Instead, we wait for the room to be joined
|
||||
|
@ -225,20 +216,12 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
override fun onFailure(failure: Throwable) {
|
||||
// Notify the user
|
||||
_viewEvents.post(RoomDirectoryViewEvents.Failure(failure))
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(action.roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(action.roomId) }
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
currentTask?.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,5 +19,5 @@ package im.vector.riotx.features.roomdirectory.roompreview
|
|||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class RoomPreviewAction : VectorViewModelAction {
|
||||
data class Join(val roomAlias: String?) : RoomPreviewAction()
|
||||
object Join : RoomPreviewAction()
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.content.Intent
|
|||
import android.os.Parcelable
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.addFragment
|
||||
|
@ -35,7 +36,8 @@ data class RoomPreviewData(
|
|||
val roomAlias: String?,
|
||||
val topic: String?,
|
||||
val worldReadable: Boolean,
|
||||
val avatarUrl: String?
|
||||
val avatarUrl: String?,
|
||||
val homeServer: String?
|
||||
) : Parcelable {
|
||||
val matrixItem: MatrixItem
|
||||
get() = MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
|
||||
|
@ -46,7 +48,7 @@ class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
companion object {
|
||||
private const val ARG = "ARG"
|
||||
|
||||
fun getIntent(context: Context, publicRoom: PublicRoom): Intent {
|
||||
fun getIntent(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData): Intent {
|
||||
return Intent(context, RoomPreviewActivity::class.java).apply {
|
||||
putExtra(ARG, RoomPreviewData(
|
||||
roomId = publicRoom.roomId,
|
||||
|
@ -54,7 +56,8 @@ class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
roomAlias = publicRoom.getPrimaryAlias(),
|
||||
topic = publicRoom.topic,
|
||||
worldReadable = publicRoom.worldReadable,
|
||||
avatarUrl = publicRoom.avatarUrl
|
||||
avatarUrl = publicRoom.avatarUrl,
|
||||
homeServer = roomDirectoryData.homeServer
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
|
|||
|
||||
roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback {
|
||||
override fun onButtonClicked() {
|
||||
roomPreviewViewModel.handle(RoomPreviewAction.Join(roomPreviewData.roomAlias))
|
||||
roomPreviewViewModel.handle(RoomPreviewAction.Join)
|
||||
}
|
||||
|
||||
override fun onRetryClicked() {
|
||||
|
|
|
@ -22,7 +22,9 @@ import com.airbnb.mvrx.ViewModelContext
|
|||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||
import im.vector.matrix.rx.rx
|
||||
|
@ -32,7 +34,7 @@ import im.vector.riotx.core.platform.VectorViewModel
|
|||
import im.vector.riotx.features.roomdirectory.JoinState
|
||||
import timber.log.Timber
|
||||
|
||||
class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: RoomPreviewViewState,
|
||||
class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
|
@ -52,30 +54,41 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
|
|||
|
||||
init {
|
||||
// Observe joined room (from the sync)
|
||||
observeJoinedRooms()
|
||||
observeRoomSummary()
|
||||
observeMembershipChanges()
|
||||
}
|
||||
|
||||
private fun observeJoinedRooms() {
|
||||
private fun observeRoomSummary() {
|
||||
val queryParams = roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
roomId = QueryStringValue.Equals(initialState.roomId)
|
||||
}
|
||||
session
|
||||
.rx()
|
||||
.liveRoomSummaries(queryParams)
|
||||
.subscribe { list ->
|
||||
withState { state ->
|
||||
val isRoomJoined = list
|
||||
?.map { it.roomId }
|
||||
?.toList()
|
||||
?.contains(state.roomId) == true
|
||||
val isRoomJoined = list.any {
|
||||
it.membership == Membership.JOIN
|
||||
}
|
||||
if (isRoomJoined) {
|
||||
setState { copy(roomJoinState = JoinState.JOINED) }
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
if (isRoomJoined) {
|
||||
setState {
|
||||
copy(
|
||||
roomJoinState = JoinState.JOINED
|
||||
)
|
||||
}
|
||||
}
|
||||
private fun observeMembershipChanges() {
|
||||
session.rx()
|
||||
.liveRoomChangeMembershipState()
|
||||
.subscribe {
|
||||
val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown
|
||||
val joinState = when (changeMembership) {
|
||||
is ChangeMembershipState.Joining -> JoinState.JOINING
|
||||
is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR
|
||||
// Other cases are handled by room summary
|
||||
else -> null
|
||||
}
|
||||
if (joinState != null) {
|
||||
setState { copy(roomJoinState = joinState) }
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
|
@ -83,37 +96,27 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
|
|||
|
||||
override fun handle(action: RoomPreviewAction) {
|
||||
when (action) {
|
||||
is RoomPreviewAction.Join -> handleJoinRoom(action)
|
||||
is RoomPreviewAction.Join -> handleJoinRoom()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleJoinRoom(action: RoomPreviewAction.Join) = withState { state ->
|
||||
private fun handleJoinRoom() = withState { state ->
|
||||
if (state.roomJoinState == JoinState.JOINING) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to join an already joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
roomJoinState = JoinState.JOINING,
|
||||
lastError = null
|
||||
)
|
||||
}
|
||||
|
||||
session.joinRoom(action.roomAlias ?: state.roomId, callback = object : MatrixCallback<Unit> {
|
||||
val viaServers = state.homeServer?.let {
|
||||
listOf(it)
|
||||
} ?: emptyList()
|
||||
session.joinRoom(state.roomId, viaServers = viaServers, callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
|
||||
// Instead, we wait for the room to be joined
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
roomJoinState = JoinState.JOINING_ERROR,
|
||||
lastError = failure
|
||||
)
|
||||
}
|
||||
setState { copy(lastError = failure) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -22,11 +22,21 @@ import im.vector.riotx.features.roomdirectory.JoinState
|
|||
data class RoomPreviewViewState(
|
||||
// The room id
|
||||
val roomId: String = "",
|
||||
val roomAlias: String? = null,
|
||||
/**
|
||||
* The server name (might be null)
|
||||
* Set null when the server is the current user's home server.
|
||||
*/
|
||||
val homeServer: String? = null,
|
||||
// Current state of the room in preview
|
||||
val roomJoinState: JoinState = JoinState.NOT_JOINED,
|
||||
// Last error of join room request
|
||||
val lastError: Throwable? = null
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomPreviewData) : this(roomId = args.roomId)
|
||||
constructor(args: RoomPreviewData) : this(
|
||||
roomId = args.roomId,
|
||||
roomAlias = args.roomAlias,
|
||||
homeServer = args.homeServer
|
||||
)
|
||||
}
|
||||
|
|
|
@ -112,7 +112,6 @@ class RoomProfileFragment @Inject constructor(
|
|||
when (it) {
|
||||
is RoomProfileViewEvents.Loading -> showLoading(it.message)
|
||||
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
|
||||
is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
||||
is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
|
||||
RoomProfileViewEvents.OnChangeAvatarSuccess -> dismissLoadingDialog()
|
||||
}.exhaustive
|
||||
|
|
|
@ -25,7 +25,6 @@ sealed class RoomProfileViewEvents : VectorViewEvents {
|
|||
data class Loading(val message: CharSequence? = null) : RoomProfileViewEvents()
|
||||
data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
|
||||
|
||||
object OnLeaveRoomSuccess : RoomProfileViewEvents()
|
||||
object OnChangeAvatarSuccess : RoomProfileViewEvents()
|
||||
data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents()
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.squareup.inject.assisted.AssistedInject
|
|||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
|
@ -71,7 +72,9 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
|||
powerLevelsContentLive
|
||||
.subscribe {
|
||||
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||
setState { copy(canChangeAvatar = powerLevelsHelper.isUserAbleToChangeRoomAvatar(session.myUserId)) }
|
||||
setState {
|
||||
copy(canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR))
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
@ -95,7 +98,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
|||
_viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room)))
|
||||
room.leave(null, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_viewEvents.post(RoomProfileViewEvents.OnLeaveRoomSuccess)
|
||||
// Do nothing, we will be closing the room automatically when it will get back from sync
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
||||
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||
|
@ -104,6 +105,13 @@ class RoomSettingsController @Inject constructor(
|
|||
action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() }
|
||||
)
|
||||
|
||||
buildEncryptionAction(data.actionPermissions, roomSummary)
|
||||
}
|
||||
|
||||
private fun buildEncryptionAction(actionPermissions: RoomSettingsViewState.ActionPermissions, roomSummary: RoomSummary) {
|
||||
if (!actionPermissions.canEnableEncryption) {
|
||||
return
|
||||
}
|
||||
if (roomSummary.isEncrypted) {
|
||||
buildProfileAction(
|
||||
id = "encryption",
|
||||
|
|
|
@ -101,10 +101,13 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
.subscribe {
|
||||
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||
val permissions = RoomSettingsViewState.ActionPermissions(
|
||||
canChangeName = powerLevelsHelper.isUserAbleToChangeRoomName(session.myUserId),
|
||||
canChangeTopic = powerLevelsHelper.isUserAbleToChangeRoomTopic(session.myUserId),
|
||||
canChangeCanonicalAlias = powerLevelsHelper.isUserAbleToChangeRoomCanonicalAlias(session.myUserId),
|
||||
canChangeHistoryReadability = powerLevelsHelper.isUserAbleToChangeRoomHistoryReadability(session.myUserId)
|
||||
canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME),
|
||||
canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC),
|
||||
canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||
EventType.STATE_ROOM_CANONICAL_ALIAS),
|
||||
canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY),
|
||||
canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
||||
)
|
||||
setState { copy(actionPermissions = permissions) }
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ data class RoomSettingsViewState(
|
|||
val canChangeName: Boolean = false,
|
||||
val canChangeTopic: Boolean = false,
|
||||
val canChangeCanonicalAlias: Boolean = false,
|
||||
val canChangeHistoryReadability: Boolean = false
|
||||
val canChangeHistoryReadability: Boolean = false,
|
||||
val canEnableEncryption: Boolean = false
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.widgets
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Fail
|
||||
|
@ -236,7 +237,9 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
|
|||
_viewEvents.post(WidgetViewEvents.OnURLFormatted(formattedUrl))
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is WidgetManagementFailure.TermsNotSignedException) {
|
||||
_viewEvents.post(WidgetViewEvents.DisplayTerms(initialState.baseUrl, failure.token))
|
||||
// Terms for IM shouldn't have path appended
|
||||
val displayTermsBaseUrl = Uri.parse(initialState.baseUrl).buildUpon().path("").toString()
|
||||
_viewEvents.post(WidgetViewEvents.DisplayTerms(displayTermsBaseUrl, failure.token))
|
||||
}
|
||||
setState { copy(formattedURL = Fail(failure)) }
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/first_names" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/composer_related_message_preview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/createDirectRoomTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/createDirectRoomTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/createRoomTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
android:contentDescription="@string/a11y_open_drawer"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/groupToolbarTitleView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/knownUsersTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
tools:ignore="MissingConstraints"
|
||||
tools:src="@drawable/ic_shield_trusted" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/matrixProfileToolbarTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
tools:ignore="MissingConstraints"
|
||||
tools:src="@drawable/ic_shield_trusted" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomToolbarTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -60,7 +60,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/matrix.json/data/roomName" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomToolbarSubtitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomPreviewNoPreviewToolbarTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomSettingsToolbarTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
tools:ignore="MissingConstraints"
|
||||
tools:src="@drawable/ic_shield_trusted" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomUploadsToolbarTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/userDirectoryTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
android:textStyle="bold"
|
||||
tools:text="name" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemAutocompleteEmojiSubname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_room_actions_notifications_all" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/actionTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
app:layout_constraintVertical_bias="0"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_message_preview_sender"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -41,7 +41,7 @@
|
|||
app:layout_constraintTop_toTopOf="@id/bottom_sheet_message_preview_avatar"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_message_preview_body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
app:layout_constraintVertical_bias="0"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/bottomSheetRoomPreviewName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/createDirectRoomUserName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -54,7 +54,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/createDirectRoomUserID"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemDeviceId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="@dimen/item_form_min_height">
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/formSwitchTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/groupNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/knownUserName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -54,7 +54,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/knownUserID"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
tools:src="@drawable/ic_room_profile_notification"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/actionTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -47,7 +47,7 @@
|
|||
app:layout_goneMarginStart="0dp"
|
||||
tools:text="@string/room_profile_section_security_learn_more" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/actionSubtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
tools:ignore="MissingConstraints"
|
||||
tools:src="@drawable/ic_shield_trusted" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/matrixItemTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -55,7 +55,7 @@
|
|||
app:layout_goneMarginStart="0dp"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/matrixItemSubtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemPublicRoomName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
|
||||
tools:layout_marginStart="20dp" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomNameView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -138,7 +138,7 @@
|
|||
app:layout_constraintTop_toTopOf="@+id/roomNameView"
|
||||
tools:text="@tools:sample/date/hhmm" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomLastEventView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -154,7 +154,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/roomNameView"
|
||||
tools:text="@sample/matrix.json/data/message" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomTypingView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
android:paddingRight="@dimen/layout_horizontal_margin"
|
||||
android:paddingBottom="4dp">
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomCategoryTitleView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemRoomDirectoryName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -43,7 +43,7 @@
|
|||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemRoomDirectoryDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAvatarImageView"
|
||||
tools:layout_marginStart="20dp" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomInvitationNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -54,7 +54,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/roomInvitationSubTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
android:layout_marginTop="4dp"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/messageMemberNameView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/uploadsFileTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -35,7 +35,7 @@
|
|||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Filename.file" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/uploadsFileSubtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemUserId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -38,7 +38,7 @@
|
|||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@sample/matrix.json/data/mxid" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/itemUserName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
tools:text="@tools:sample/first_names"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
<TextView
|
||||
android:id="@+id/composer_related_message_preview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -57,34 +57,35 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/inviteIdentifierView" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/inviteRejectView"
|
||||
style="@style/VectorButtonStyleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:minWidth="120dp"
|
||||
android:text="@string/reject"
|
||||
app:layout_constraintEnd_toStartOf="@+id/inviteAcceptView"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/inviteLabelView" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
<im.vector.riotx.core.platform.ButtonStateView
|
||||
android:id="@+id/inviteAcceptView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:minWidth="120dp"
|
||||
android:text="@string/accept"
|
||||
app:bsv_button_text="@string/accept"
|
||||
app:bsv_loaded_image_src="@drawable/ic_tick"
|
||||
app:bsv_use_flat_button="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@+id/inviteRejectView"
|
||||
app:layout_constraintTop_toTopOf="@id/inviteRejectView" />
|
||||
|
||||
<im.vector.riotx.core.platform.ButtonStateView
|
||||
android:id="@+id/inviteRejectView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:minWidth="120dp"
|
||||
app:bsv_button_text="@string/reject"
|
||||
app:bsv_loaded_image_src="@drawable/ic_tick"
|
||||
app:bsv_use_flat_button="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/inviteAcceptView"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/inviteLabelView"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminateOnly="true"
|
||||
android:scaleType="center"
|
||||
tools:layout_gravity="center_horizontal"
|
||||
tools:layout_marginTop="80dp" />
|
||||
|
|
Loading…
Reference in a new issue