From 0855806ae200b5f71c407e8f67b5eeacdc0d09ed Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 7 Jul 2020 22:14:11 +0200 Subject: [PATCH 01/20] Fix edit being stuck --- vector/build.gradle | 3 ++- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 17 +++++++---------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 59ae3d35de..86ffb8f9da 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -289,7 +289,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" diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 8c075004a9..e0a1b10f9e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -636,7 +636,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) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 62830a1c63..6276089145 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -846,17 +846,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) { From e8dbed1642197037cd1e470870b5c78c0f841391 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Jul 2020 14:51:15 +0200 Subject: [PATCH 02/20] Fix relations on encrypted room --- .../session/room/EventRelationsAggregationProcessor.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt index bde0cc512d..5214317f3b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt @@ -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()?.let { if (encryptedEventContent.relatesTo.type == RelationType.REPLACE) { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") From 3aabb17ea5ea9e9c76d03558fc2ac5a1a429d733 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Jul 2020 15:51:00 +0200 Subject: [PATCH 03/20] Fix timeline pagination when no displayable events --- .../internal/session/room/timeline/DefaultTimeline.kt | 5 ++++- .../session/room/timeline/TokenChunkEventPersistor.kt | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 16c98770e2..567698668b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -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) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 8e0e4759e9..87bff38587 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -241,12 +241,12 @@ 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, From 85e8e652f1ca2ee161c9af0dc2ade7eb5a2425bc Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Jul 2020 17:32:54 +0200 Subject: [PATCH 04/20] Fix IM terms of review path --- .../java/im/vector/riotx/features/widgets/WidgetViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt index d516137bc5..89d597c4dc 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt @@ -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)) } } From 6ebedaf540e63150a60649ca33fce7df72b1ea7d Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Jul 2020 17:40:37 +0200 Subject: [PATCH 05/20] Update CHANGES --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 04ac45c3cc..da6f0abf44 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,7 +10,9 @@ Improvements 🙌: - Handling (almost) properly the groups fetching (#1634) 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) Translations 🗣: - From 75c2dfcd48e8fe4844ef358605b6c5340b45f30f Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Jul 2020 19:16:22 +0200 Subject: [PATCH 06/20] Fix user data being affected by local room member event changes --- .../android/api/session/events/model/UnsignedData.kt | 7 ++++++- .../session/room/membership/RoomMemberEventHandler.kt | 2 +- .../room/detail/timeline/format/NoticeEventFormatter.kt | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt index b179cb7a31..16ff36ea07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt @@ -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? = 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 + ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt index d7d578b635..b225895532 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt @@ -34,7 +34,7 @@ internal class RoomMemberEventHandler @Inject constructor() { val userId = event.stateKey ?: return false val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember) realm.insertOrUpdate(roomMemberEntity) - if (roomMember.membership.isActive()) { + if (roomMember.membership.isActive() && event.unsignedData?.replacesState.isNullOrEmpty()) { val userEntity = UserEntityFactory.create(userId, roomMember) realm.insertOrUpdate(userEntity) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c1f4187e0b..fff74e0328 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -92,7 +92,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active 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() userIds.addAll(powerLevelsContent.users.keys) userIds.addAll(previousPowerLevelsContent.users.keys) @@ -120,7 +120,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active 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()) { @@ -294,7 +294,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active 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) @@ -305,7 +305,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active 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() From 548879bd9f8f91b4423f20d5b0905fc406f3d5a1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Jul 2020 17:20:51 +0200 Subject: [PATCH 07/20] Fix encryption enabling visible for all users --- CHANGES.md | 1 + .../room/powerlevels/PowerLevelsHelper.kt | 56 ------------------- .../roomprofile/RoomProfileViewModel.kt | 5 +- .../settings/RoomSettingsController.kt | 8 +++ .../settings/RoomSettingsViewModel.kt | 9 +-- .../settings/RoomSettingsViewState.kt | 3 +- 6 files changed, 20 insertions(+), 62 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index da6f0abf44..e0a0085af5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ 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) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/powerlevels/PowerLevelsHelper.kt index 6361a46bac..f434859f6e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/powerlevels/PowerLevelsHelper.kt @@ -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 - } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index 373dd6b56c..925ab716a4 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -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, isState = true, eventType = EventType.STATE_ROOM_AVATAR)) + } } .disposeOnClear() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt index 94177159f0..e9d2e5ccb5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt @@ -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", diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index e198375cfb..4aa4a437ac 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -101,10 +101,11 @@ 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, isState = true, eventType = EventType.STATE_ROOM_NAME), + canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_TOPIC), + canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_CANONICAL_ALIAS), + canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY), + canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_ENCRYPTION) ) setState { copy(actionPermissions = permissions) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt index a86fbf8cfa..c8d81f3ead 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt @@ -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 ) } From da7c971927fb3b619d0c30945ae9fe951589015d Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Jul 2020 17:46:59 +0200 Subject: [PATCH 08/20] Fragments: use commitTransaction instead of commitNow --- .../im/vector/riotx/core/extensions/Activity.kt | 8 ++++---- .../im/vector/riotx/core/extensions/Fragment.kt | 16 ++++++++-------- .../riotx/features/home/HomeDetailFragment.kt | 3 ++- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt index b74f143e17..cc6eb54154 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt @@ -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 VectorBaseActivity.addFragment(frameId: Int, fragmentClass: Class, 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 VectorBaseActivity.replaceFragment(frameId: Int, fragmentClass: Class, params: Parcelable? = null, tag: String? = null) { - supportFragmentManager.commitTransactionNow { + supportFragmentManager.commitTransaction { replace(frameId, fragmentClass, params.toMvRxBundle(), tag) } } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt index c28dcf12d3..88c96578ae 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt @@ -21,21 +21,21 @@ import androidx.fragment.app.Fragment import im.vector.riotx.core.platform.VectorBaseFragment fun VectorBaseFragment.addFragment(frameId: Int, fragment: Fragment) { - parentFragmentManager.commitTransactionNow { add(frameId, fragment) } + parentFragmentManager.commitTransaction { add(frameId, fragment) } } fun VectorBaseFragment.addFragment(frameId: Int, fragmentClass: Class, 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 VectorBaseFragment.replaceFragment(frameId: Int, fragmentClass: Class, params: Parcelable? = null, tag: String? = null) { - parentFragmentManager.commitTransactionNow { + parentFragmentManager.commitTransaction { replace(frameId, fragmentClass, params.toMvRxBundle(), tag) } } @@ -51,21 +51,21 @@ fun 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 VectorBaseFragment.addChildFragment(frameId: Int, fragmentClass: Class, 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 VectorBaseFragment.replaceChildFragment(frameId: Int, fragmentClass: Class, params: Parcelable? = null, tag: String? = null) { - childFragmentManager.commitTransactionNow { + childFragmentManager.commitTransaction { replace(frameId, fragmentClass, params.toMvRxBundle(), tag) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index c92c28079f..435ff7a9ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -32,6 +32,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.commitTransaction import im.vector.riotx.core.extensions.commitTransactionNow import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.platform.ToolbarConfigurable @@ -275,7 +276,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 { From 9c595b6c027f92dd3c7cd018cad9b6cd30b1b637 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Jul 2020 08:54:41 +0200 Subject: [PATCH 09/20] Fix "Leave room only leaves the current version" --- CHANGES.md | 1 + .../room/membership/leaving/LeaveRoomTask.kt | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e0a0085af5..0be18b8927 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ Bugfix 🐛: - 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) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index 08eb71fc89..2d56e5bd47 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -16,8 +16,13 @@ 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.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.state.StateEventDataSource import im.vector.matrix.android.internal.task.Task import org.greenrobot.eventbus.EventBus import javax.inject.Inject @@ -31,12 +36,22 @@ internal interface LeaveRoomTask : Task { internal class DefaultLeaveRoomTask @Inject constructor( private val roomAPI: RoomAPI, - private val eventBus: EventBus + private val eventBus: EventBus, + private val stateEventDataSource: StateEventDataSource ) : 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 roomCreateStateEvent = stateEventDataSource.getStateEvent(roomId, eventType = EventType.STATE_ROOM_CREATE, stateKey = QueryStringValue.NoCondition) + val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel()?.predecessor?.roomId + executeRequest(eventBus) { + apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + } + if (predecessorRoomId != null) { + leaveRoom(predecessorRoomId, reason) } } } From 8814364497360417fef16e718e7a113b78d47693 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Jul 2020 11:32:28 +0200 Subject: [PATCH 10/20] Invite: we shouldn't be able to open room details --- .../home/room/detail/RoomDetailFragment.kt | 3 +++ .../home/room/detail/RoomDetailViewModel.kt | 27 +++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e0a1b10f9e..7483d978a3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -293,6 +293,7 @@ class RoomDetailFragment @Inject constructor( setupJumpToBottomView() setupWidgetsBannerView() + roomToolbarContentView.isClickable = false roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) } @@ -858,6 +859,7 @@ class RoomDetailFragment @Inject constructor( val summary = state.asyncRoomSummary() val inviter = state.asyncInviter() if (summary?.membership == Membership.JOIN) { + roomToolbarContentView.isClickable = true roomWidgetsBannerView.render(state.activeRoomWidgets()) scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline timelineEventController.update(state) @@ -879,6 +881,7 @@ class RoomDetailFragment @Inject constructor( notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent)) } } else if (summary?.membership == Membership.INVITE && inviter != null) { + roomToolbarContentView.isClickable = false inviteView.visibility = View.VISIBLE inviteView.render(inviter, VectorInviteView.Mode.LARGE) // Intercept click event diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 6276089145..bed8b4867d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -405,17 +405,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 ***************************************************************************** From 150d44aafd3afa6639c6d8dc834b6626ab490b95 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Jul 2020 20:08:51 +0200 Subject: [PATCH 11/20] Improve a bit how joining/leaving are handled --- .../java/im/vector/matrix/rx/RxSession.kt | 5 ++ .../api/session/room/RoomDirectoryService.kt | 7 -- .../android/api/session/room/RoomService.kt | 9 +++ .../session/room/RoomSummaryQueryParams.kt | 3 + .../room/members/ChangeMembershipState.kt | 34 +++++++++ .../room/DefaultRoomDirectoryService.kt | 8 -- .../session/room/DefaultRoomService.kt | 7 ++ .../RoomChangeMembershipStateDataSource.kt | 67 +++++++++++++++++ .../room/membership/joining/JoinRoomTask.kt | 13 +++- .../room/membership/leaving/LeaveRoomTask.kt | 32 ++++++-- .../room/summary/RoomSummaryDataSource.kt | 1 + .../internal/session/sync/RoomSyncHandler.kt | 6 ++ .../home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 19 ++++- .../home/room/detail/RoomDetailViewState.kt | 2 + .../home/room/list/RoomInvitationItem.kt | 68 +++++++---------- .../home/room/list/RoomListViewModel.kt | 62 +++++----------- .../home/room/list/RoomListViewState.kt | 10 +-- .../home/room/list/RoomSummaryController.kt | 21 ++---- .../home/room/list/RoomSummaryItemFactory.kt | 23 +++--- .../invite/InviteButtonStateBinder.kt | 48 ++++++++++++ .../riotx/features/invite/VectorInviteView.kt | 26 ++++++- .../features/navigation/DefaultNavigator.kt | 5 +- .../riotx/features/navigation/Navigator.kt | 3 +- .../roomdirectory/PublicRoomsController.kt | 12 +-- .../roomdirectory/PublicRoomsFragment.kt | 24 +++--- .../roomdirectory/PublicRoomsViewState.kt | 10 +-- .../roomdirectory/RoomDirectoryAction.kt | 2 +- .../roomdirectory/RoomDirectoryViewModel.kt | 73 +++++++------------ .../roompreview/RoomPreviewAction.kt | 2 +- .../roompreview/RoomPreviewActivity.kt | 9 ++- .../RoomPreviewNoPreviewFragment.kt | 2 +- .../roompreview/RoomPreviewViewModel.kt | 69 +++++++++--------- .../roompreview/RoomPreviewViewState.kt | 12 ++- .../roomprofile/RoomProfileFragment.kt | 1 - .../roomprofile/RoomProfileViewEvents.kt | 1 - .../roomprofile/RoomProfileViewModel.kt | 2 +- .../main/res/layout/vector_invite_view.xml | 37 +++++----- .../src/main/res/layout/view_button_state.xml | 1 + 39 files changed, 454 insertions(+), 284 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index e8fef1361d..54dacb5614 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -25,6 +25,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 @@ -165,6 +166,10 @@ class RxSession(private val session: Session) { session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes) } } + + fun liveRoomChangeMembershipState(): Observable> { + return session.getChangeMembershipsLive().asObservable() + } } fun Session.rx(): RxSession { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt index 0273c789dd..7014fbff37 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt @@ -34,13 +34,6 @@ interface RoomDirectoryService { publicRoomsParams: PublicRoomsParams, callback: MatrixCallback): Cancelable - /** - * Join a room by id, or room alias - */ - fun joinRoom(roomIdOrAlias: String, - reason: String? = null, - callback: MatrixCallback): 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. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index bc6c17a130..3093fb8ec5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -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>): Cancelable + /** + * Return a live data of all local changes membership who 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> + fun getExistingDirectRoomWithUser(otherUserId: String) : Room? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt index 6983bda225..51df30ad75 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt @@ -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 @@ -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.all() fun build() = RoomSummaryQueryParams( + roomId = roomId, displayName = displayName, canonicalAlias = canonicalAlias, memberships = memberships diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt new file mode 100644 index 0000000000..8f42e310bf --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt @@ -0,0 +1,34 @@ +/* + * 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 + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt index ef55702de6..e89fae647f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt @@ -44,14 +44,6 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu .executeBy(taskExecutor) } - override fun joinRoom(roomIdOrAlias: String, reason: String?, callback: MatrixCallback): Cancelable { - return joinRoomTask - .configureWith(JoinRoomTask.Params(roomIdOrAlias, reason)) { - this.callback = callback - } - .executeBy(taskExecutor) - } - override fun getThirdPartyProtocol(callback: MatrixCallback>): Cancelable { return getThirdPartyProtocolsTask .configureWith { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index c773682c0f..b8b4c968b1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -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> { + return roomChangeMembershipStateDataSource.getLiveStates() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt new file mode 100644 index 0000000000..5cf75c3bbd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -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>(emptyMap()) + private val states = HashMap() + + /** + * 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> { + 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 + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index 635f3955c2..7467a595bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -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(eventBus) { - apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason)) + roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) + val joinRoomResponse = try { + executeRequest(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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index 2d56e5bd47..94645f3d98 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -19,12 +19,16 @@ 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 { @@ -37,7 +41,9 @@ internal interface LeaveRoomTask : Task { internal class DefaultLeaveRoomTask @Inject constructor( private val roomAPI: RoomAPI, private val eventBus: EventBus, - private val stateEventDataSource: StateEventDataSource + private val stateEventDataSource: StateEventDataSource, + private val roomSummaryDataSource: RoomSummaryDataSource, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource ) : LeaveRoomTask { override suspend fun execute(params: LeaveRoomTask.Params) { @@ -45,13 +51,29 @@ internal class DefaultLeaveRoomTask @Inject constructor( } private suspend fun leaveRoom(roomId: String, reason: String?) { - val roomCreateStateEvent = stateEventDataSource.getStateEvent(roomId, eventType = EventType.STATE_ROOM_CREATE, stateKey = QueryStringValue.NoCondition) - val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel()?.predecessor?.roomId - executeRequest(eventBus) { - apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + 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()?.predecessor?.roomId if (predecessorRoomId != null) { leaveRoom(predecessorRoomId, reason) } + try { + executeRequest(eventBus) { + apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + } + } catch (failure: Throwable) { + roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.FailedLeaving(failure)) + throw failure + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt index 7c579a2719..b1518b085d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt @@ -100,6 +100,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { 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) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index f3af24001d..de2c3cda57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -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 } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7483d978a3..2f8c72a996 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -883,7 +883,7 @@ class RoomDetailFragment @Inject constructor( } else if (summary?.membership == Membership.INVITE && inviter != null) { roomToolbarContentView.isClickable = false 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) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index bed8b4867d..fb1cb8e666 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -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() @@ -406,7 +408,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled() fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state -> - if(state.asyncRoomSummary()?.membership != Membership.JOIN){ + if (state.asyncRoomSummary()?.membership != Membership.JOIN) { return@withState false } when (itemId) { @@ -629,7 +631,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { - session.joinRoom(command.roomAlias, command.reason, object : MatrixCallback { + session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback { override fun onSuccess(data: Unit) { session.getRoomSummary(command.roomAlias) ?.roomId @@ -1147,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) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt index 224dd61b65..6800850c48 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt index 4e4e758aa2..7338a46d8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt @@ -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() { @@ -37,53 +38,36 @@ abstract class RoomInvitationItem : VectorEpoxyModel( @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) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index a2de7c79a0..cfc76b61a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -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 { 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 { 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): RoomSummaries { // Set up init size on directChats and groupRooms as they are the biggest ones val invites = ArrayList() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt index b41b4b9eeb..63f0cf2a1a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt @@ -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> = Uninitialized, val roomFilter: String = "", val asyncFilteredRooms: Async = Uninitialized, - // List of roomIds that the user wants to join - val joiningRoomsIds: Set = emptySet(), - // List of roomIds that the user wants to join, but an error occurred - val joiningErrorRoomsIds: Set = emptySet(), - // List of roomIds that the user wants to join - val rejectingRoomsIds: Set = emptySet(), - // List of roomIds that the user wants to reject, but an error occurred - val rejectingErrorRoomsIds: Set = emptySet(), + val roomMembershipChanges: Map = emptyMap(), val isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt index b06cb8a4bb..efa19d012b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt @@ -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, - joiningRoomsIds: Set, - joiningErrorRoomsIds: Set, - rejectingRoomsIds: Set, - rejectingErrorRoomsIds: Set, + roomChangedMembershipStates: Map, selectedRoomIds: Set) { summaries.forEach { roomSummary -> roomSummaryItemFactory .create(roomSummary, - joiningRoomsIds, - joiningErrorRoomsIds, - rejectingRoomsIds, - rejectingErrorRoomsIds, + roomChangedMembershipStates, selectedRoomIds, listener) .addTo(this) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index 1830899d80..f33166504d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -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, - joiningErrorRoomsIds: Set, - rejectingRoomsIds: Set, - rejectingErrorRoomsIds: Set, + roomChangeMembershipStates: Map, selectedRoomIds: Set, 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, - joiningErrorRoomsIds: Set, - rejectingRoomsIds: Set, - rejectingErrorRoomsIds: Set, + 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) } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt new file mode 100644 index 0000000000..88abf28888 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt @@ -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) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt b/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt index b9bd9b0e1e..42f440fc30 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 0b89ab8ec4..7b3eedc71f 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -29,6 +29,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 @@ -159,8 +160,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) } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index ce4d5ef3ea..08fd63b93c 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -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.util.MatrixItem import im.vector.matrix.android.api.session.widgets.model.Widget @@ -48,7 +49,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 = "") diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt index aaacb2a170..04ddccdd8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index 869ee85337..dcccd33cf6 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt index 665e37dcbd..67b17ea34e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt @@ -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> = 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 = emptySet(), - // Set of roomIds that the user wants to join, but an error occurred - val joiningErrorRoomsIds: Set = emptySet(), // Set of joined roomId, val joinedRoomsIds: Set = emptySet(), - val roomDirectoryDisplayName: String? = null + // keys are room alias or roomId + val changeMembershipStates: Map = emptyMap(), + val roomDirectoryData: RoomDirectoryData = RoomDirectoryData() ) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt index 598f26fc3b..8b32726370 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index 53661b075a..96de55a5b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -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 { + val viaServers = state.roomDirectoryData.homeServer?.let { + listOf(it) + } ?: emptyList() + session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback { 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() } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt index 6b83ada90e..426078fa3d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt index 3cb442127f..063cf3b8ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt @@ -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 )) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 04ecdb2305..ee01e8f7fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 3f8ae03029..c5e79832fc 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -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(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 { + val viaServers = state.homeServer?.let { + listOf(it) + } ?: emptyList() + session.joinRoom(state.roomId, viaServers = viaServers, callback = object : MatrixCallback { 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) } } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt index d3c75f95e0..04806ccf27 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt @@ -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 + ) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index aa414ec2a1..1ff5094517 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt index 78df127f72..c0c1f2eb24 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index 925ab716a4..d673422d06 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -98,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 { 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) { diff --git a/vector/src/main/res/layout/vector_invite_view.xml b/vector/src/main/res/layout/vector_invite_view.xml index 5e557895c2..7356fcf64b 100644 --- a/vector/src/main/res/layout/vector_invite_view.xml +++ b/vector/src/main/res/layout/vector_invite_view.xml @@ -57,34 +57,35 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/inviteIdentifierView" /> - - - + + From e07a584d668e967d382f9bf1cffc0b734aaba7c5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Jul 2020 20:09:30 +0200 Subject: [PATCH 12/20] Revert fixing users as it's not the good catch --- .../internal/session/room/membership/RoomMemberEventHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt index b225895532..d7d578b635 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt @@ -34,7 +34,7 @@ internal class RoomMemberEventHandler @Inject constructor() { val userId = event.stateKey ?: return false val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember) realm.insertOrUpdate(roomMemberEntity) - if (roomMember.membership.isActive() && event.unsignedData?.replacesState.isNullOrEmpty()) { + if (roomMember.membership.isActive()) { val userEntity = UserEntityFactory.create(userId, roomMember) realm.insertOrUpdate(userEntity) } From 253582219c6e4e17def3f7beb08c94d5c42e9f66 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Jul 2020 22:35:51 +0200 Subject: [PATCH 13/20] Remove EllipsizingTextView as it provokes more issue than it solves --- .../core/platform/EllipsizingTextView.kt | 421 ------------------ ...onstraint_set_composer_layout_expanded.xml | 2 +- .../layout/fragment_create_direct_room.xml | 2 +- ...ent_create_direct_room_directory_users.xml | 2 +- .../main/res/layout/fragment_create_room.xml | 2 +- .../main/res/layout/fragment_home_detail.xml | 2 +- .../main/res/layout/fragment_known_users.xml | 2 +- .../res/layout/fragment_matrix_profile.xml | 2 +- .../main/res/layout/fragment_room_detail.xml | 4 +- .../fragment_room_preview_no_preview.xml | 2 +- .../layout/fragment_room_setting_generic.xml | 2 +- .../main/res/layout/fragment_room_uploads.xml | 2 +- .../res/layout/fragment_user_directory.xml | 2 +- .../res/layout/item_autocomplete_emoji.xml | 2 +- .../res/layout/item_bottom_sheet_action.xml | 2 +- .../item_bottom_sheet_message_preview.xml | 4 +- .../layout/item_bottom_sheet_room_preview.xml | 2 +- .../layout/item_create_direct_room_user.xml | 4 +- vector/src/main/res/layout/item_device.xml | 2 +- .../src/main/res/layout/item_form_switch.xml | 2 +- vector/src/main/res/layout/item_group.xml | 2 +- .../src/main/res/layout/item_known_user.xml | 4 +- .../main/res/layout/item_profile_action.xml | 4 +- .../res/layout/item_profile_matrix_item.xml | 4 +- .../src/main/res/layout/item_public_room.xml | 2 +- vector/src/main/res/layout/item_room.xml | 6 +- .../main/res/layout/item_room_category.xml | 2 +- .../main/res/layout/item_room_directory.xml | 4 +- .../main/res/layout/item_room_invitation.xml | 4 +- .../res/layout/item_timeline_event_base.xml | 2 +- .../src/main/res/layout/item_uploads_file.xml | 4 +- vector/src/main/res/layout/item_user.xml | 4 +- .../main/res/layout/merge_composer_layout.xml | 2 +- 33 files changed, 44 insertions(+), 465 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/core/platform/EllipsizingTextView.kt diff --git a/vector/src/main/java/im/vector/riotx/core/platform/EllipsizingTextView.kt b/vector/src/main/java/im/vector/riotx/core/platform/EllipsizingTextView.kt deleted file mode 100644 index f54776fc40..0000000000 --- a/vector/src/main/java/im/vector/riotx/core/platform/EllipsizingTextView.kt +++ /dev/null @@ -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 = 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) - } -} diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml b/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml index 17b350542a..198f4ca83b 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml @@ -60,7 +60,7 @@ app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/first_names" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Date: Sat, 11 Jul 2020 10:48:45 +0200 Subject: [PATCH 14/20] KeybackupBanner: remove unnecessary animation --- .../main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt index d0cea6194b..d457099087 100755 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt @@ -80,8 +80,6 @@ class KeysBackupBanner @JvmOverloads constructor( state = newState hideAll() - val parent = parent as ViewGroup - TransitionManager.beginDelayedTransition(parent) when (newState) { State.Initial -> renderInitial() State.Hidden -> renderHidden() From 2f0645a94e85dd1131478bf9fad93745dccfda61 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 11 Jul 2020 12:39:41 +0200 Subject: [PATCH 15/20] Fix left user has no name in db --- .../database/helper/ChunkEntityHelper.kt | 9 ++++---- .../database/model/RoomMemberSummaryEntity.kt | 2 +- .../room/membership/RoomMemberEventHandler.kt | 9 +++++++- .../internal/session/sync/RoomSyncHandler.kt | 23 ++++++++++++++++--- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index bc681e4eb8..a2965df27b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -123,17 +123,18 @@ private fun computeIsUnique( realm: Realm, roomId: String, isLastForward: Boolean, - myRoomMemberContent: RoomMemberContent, + senderRoomMemberContent: RoomMemberContent, roomMemberContentsByUser: Map ): 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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberSummaryEntity.kt index 45bf1b3a22..e2a9af649e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberSummaryEntity.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt index d7d578b635..b340766c1b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEventHandler.kt @@ -30,8 +30,15 @@ internal class RoomMemberEventHandler @Inject constructor() { if (event.type != EventType.STATE_ROOM_MEMBER) { return false } - val roomMember = event.content.toModel() ?: return false val userId = event.stateKey ?: return false + val roomMember = event.content.toModel() + 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()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index de2c3cda57..6f27da2c5c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -32,6 +32,7 @@ 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 @@ -313,14 +314,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) @@ -411,4 +413,19 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } } + + private fun Event.getFixedRoomMemberContent(): RoomMemberContent?{ + val content = content.toModel() + // if user is leaving, we should grab his last name and avatar from prevContent + return if (content?.membership?.isLeft() == true) { + val prevContent = resolvedPrevContent().toModel() + content.copy( + displayName = prevContent?.displayName, + avatarUrl = prevContent?.avatarUrl + ) + } else { + content + } + } + } From 1c17bd9f5a957560d740986c75b231ce16577434 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 11 Jul 2020 12:57:49 +0200 Subject: [PATCH 16/20] Clean code --- .../matrix/android/api/session/room/RoomService.kt | 2 +- .../session/room/members/ChangeMembershipState.kt | 1 - .../room/timeline/TokenChunkEventPersistor.kt | 3 ++- .../android/internal/session/sync/RoomSyncHandler.kt | 4 +--- .../vector/riotx/core/ui/views/KeysBackupBanner.kt | 2 -- .../vector/riotx/features/home/HomeDetailFragment.kt | 1 - .../room/detail/JumpToBottomViewVisibilityManager.kt | 2 -- .../features/roomdirectory/RoomDirectoryViewModel.kt | 2 +- .../features/roomprofile/RoomProfileViewModel.kt | 2 +- .../roomprofile/settings/RoomSettingsViewModel.kt | 12 +++++++----- 10 files changed, 13 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 3093fb8ec5..813d7d98b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -111,7 +111,7 @@ interface RoomService { * 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> + fun getChangeMembershipsLive(): LiveData> fun getExistingDirectRoomWithUser(otherUserId: String) : Room? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt index 8f42e310bf..1094f9cb21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt @@ -30,5 +30,4 @@ sealed class ChangeMembershipState() { fun isSuccessful() = this is Joined || this is Left fun isFailed() = this is FailedJoining || this is FailedLeaving - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 87bff38587..b0c697ee6c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -245,7 +245,8 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri it.deleteOnCascade() } val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) - val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null || (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS) + val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null + || (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS) if (shouldUpdateSummary) { val latestPreviewableEvent = TimelineEventEntity.latestEvent( realm, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index 6f27da2c5c..df4f52bcc9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -31,7 +31,6 @@ 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 @@ -414,7 +413,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } - private fun Event.getFixedRoomMemberContent(): RoomMemberContent?{ + private fun Event.getFixedRoomMemberContent(): RoomMemberContent? { val content = content.toModel() // if user is leaving, we should grab his last name and avatar from prevContent return if (content?.membership?.isLeft() == true) { @@ -427,5 +426,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle content } } - } diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt index d457099087..0152f7c2a8 100755 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index e7ee8ca577..5da5049045 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -32,7 +32,6 @@ 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.commitTransaction -import im.vector.riotx.core.extensions.commitTransactionNow import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/JumpToBottomViewVisibilityManager.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/JumpToBottomViewVisibilityManager.kt index 4be5502678..50a28b8a8b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/JumpToBottomViewVisibilityManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/JumpToBottomViewVisibilityManager.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index 96de55a5b8..1b51ab1822 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -112,7 +112,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: if (it.roomDirectoryData == action.roomDirectoryData) { return@withState } - setState{ + setState { copy(roomDirectoryData = action.roomDirectoryData) } reset("") diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index d673422d06..bab0331ccb 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -73,7 +73,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini .subscribe { val powerLevelsHelper = PowerLevelsHelper(it) setState { - copy(canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_AVATAR)) + copy(canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR)) } } .disposeOnClear() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index 4aa4a437ac..652c5cf4c5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -101,11 +101,13 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: .subscribe { val powerLevelsHelper = PowerLevelsHelper(it) val permissions = RoomSettingsViewState.ActionPermissions( - canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_NAME), - canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_TOPIC), - canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_CANONICAL_ALIAS), - canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY), - canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, isState = true, eventType = EventType.STATE_ROOM_ENCRYPTION) + 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) } } From 0412fabbd2e2ab94f3346c467aaf13e1f1bde497 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 11 Jul 2020 13:24:32 +0200 Subject: [PATCH 17/20] Clean comment on EventInsertLiveProcessor --- .../android/internal/database/EventInsertLiveObserver.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt index f0884918c0..2f7ed60bc7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt @@ -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 } } } From e7804af2f71fa9b450977ee75b983159200d39a7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 11 Jul 2020 13:27:49 +0200 Subject: [PATCH 18/20] EventInsertLiveObserver: change of delete method (should be faster) --- .../matrix/android/internal/database/EventInsertLiveObserver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt index 2f7ed60bc7..98d8806288 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt @@ -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) } } } From c2cccd8b11cd51bbdcf110b1b31373c5af713aff Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 11 Jul 2020 15:26:54 +0200 Subject: [PATCH 19/20] Some changes after benoit's review --- .../room/DefaultRoomDirectoryService.kt | 2 -- .../home/room/detail/RoomDetailFragment.kt | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt index e89fae647f..288ee603b6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index ba7e356545..62078c3053 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -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 @@ -293,7 +294,6 @@ class RoomDetailFragment @Inject constructor( setupJumpToBottomView() setupWidgetsBannerView() - roomToolbarContentView.isClickable = false roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) } @@ -854,13 +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) { - roomToolbarContentView.isClickable = true roomWidgetsBannerView.render(state.activeRoomWidgets()) + jumpToBottomView.count = summary.notificationCount + jumpToBottomView.drawBadge = summary.hasUnreadMessages scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline timelineEventController.update(state) inviteView.visibility = View.GONE @@ -881,7 +882,6 @@ class RoomDetailFragment @Inject constructor( notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent)) } } else if (summary?.membership == Membership.INVITE && inviter != null) { - roomToolbarContentView.isClickable = false inviteView.visibility = View.VISIBLE inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState) // Intercept click event @@ -891,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 From 37378ca5a604879d46f6674f5610574c4c39b3c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 21:57:45 +0200 Subject: [PATCH 20/20] typo --- .../im/vector/matrix/android/api/session/room/RoomService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 813d7d98b2..3319cecfef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -106,7 +106,7 @@ interface RoomService { callback: MatrixCallback>): Cancelable /** - * Return a live data of all local changes membership who happened since the session has been opened. + * 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