From dd65c67f1f375c988c143a969f8772590e1be96b Mon Sep 17 00:00:00 2001 From: Christoph Klassen Date: Fri, 16 Dec 2022 09:31:14 +0100 Subject: [PATCH] edits after first review on Github --- changelog.d/7762.feature | 2 +- gradle.properties | 2 +- .../src/main/res/values/strings.xml | 16 +- .../sdk/api/session/events/model/Event.kt | 4 +- .../PinnedEventsStateContent.kt | 2 +- .../api/session/room/state/StateService.kt | 19 +- .../sdk/api/session/room/timeline/Timeline.kt | 2 +- .../session/room/timeline/TimelineSettings.kt | 6 +- .../sdk/internal/session/room/RoomAPI.kt | 3 +- .../PinnedEventsStateResponse.kt | 28 --- .../session/room/state/DefaultStateService.kt | 29 ++-- .../session/room/timeline/DefaultTimeline.kt | 37 ++-- .../src/main/res/values/config-settings.xml | 2 +- vector/src/main/AndroidManifest.xml | 2 +- .../home/room/detail/RoomDetailAction.kt | 4 +- .../home/room/detail/RoomDetailViewState.kt | 6 +- .../home/room/detail/TimelineFragment.kt | 63 ++++--- .../home/room/detail/TimelineViewModel.kt | 77 ++++---- .../room/detail/arguments/TimelineArgs.kt | 4 +- .../timeline/action/EventSharedAction.kt | 10 +- .../timeline/action/MessageActionState.kt | 6 +- .../action/MessageActionsBottomSheet.kt | 4 +- .../action/MessageActionsViewModel.kt | 164 +++++++++--------- .../action/TimelineEventFragmentArgs.kt | 2 +- .../timeline/format/NoticeEventFormatter.kt | 26 +-- .../detail/timeline/merged/MergedTimelines.kt | 2 +- .../room/pinnedevents/PinnedEventsActivity.kt | 82 +++++++++ .../arguments/PinnedEventsTimelineArgs.kt} | 9 +- .../pinnedmessages/PinnedMessagesActivity.kt | 99 ----------- .../features/navigation/DefaultNavigator.kt | 10 +- .../app/features/navigation/Navigator.kt | 4 +- .../features/settings/VectorPreferences.kt | 6 +- ...messages.xml => ic_open_pinned_events.xml} | 0 vector/src/main/res/drawable/ic_pin_event.xml | 4 + .../src/main/res/drawable/ic_pin_message.xml | 9 - .../src/main/res/drawable/ic_unpin_event.xml | 4 + .../main/res/drawable/ic_unpin_message.xml | 9 - ...essages.xml => activity_pinned_events.xml} | 2 +- vector/src/main/res/menu/menu_timeline.xml | 6 +- .../src/main/res/xml/vector_settings_labs.xml | 6 +- 40 files changed, 356 insertions(+), 416 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/pinnedmessages/PinnedEventsStateResponse.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/pinnedevents/PinnedEventsActivity.kt rename vector/src/main/java/im/vector/app/features/home/room/{pinnedmessages/arguments/PinnedMessagesTimelineArgs.kt => pinnedevents/arguments/PinnedEventsTimelineArgs.kt} (70%) delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/PinnedMessagesActivity.kt rename vector/src/main/res/drawable/{ic_open_pinned_messages.xml => ic_open_pinned_events.xml} (100%) create mode 100644 vector/src/main/res/drawable/ic_pin_event.xml delete mode 100644 vector/src/main/res/drawable/ic_pin_message.xml create mode 100644 vector/src/main/res/drawable/ic_unpin_event.xml delete mode 100644 vector/src/main/res/drawable/ic_unpin_message.xml rename vector/src/main/res/layout/{activity_pinned_messages.xml => activity_pinned_events.xml} (88%) diff --git a/changelog.d/7762.feature b/changelog.d/7762.feature index 48d7fb0d2f..485acf9415 100644 --- a/changelog.d/7762.feature +++ b/changelog.d/7762.feature @@ -1 +1 @@ -Added lab feature to pin/unpin messages \ No newline at end of file +Added lab feature to pin/unpin messages diff --git a/gradle.properties b/gradle.properties index 2c999af35d..4e3c90491f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ # The setting is particularly useful for tweaking memory settings. # Build Time Optimizations -org.gradle.jvmargs=-Xmx4g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC +org.gradle.jvmargs=-Xmx4g -Xms512M -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC org.gradle.configureondemand=true org.gradle.parallel=true org.gradle.vfs.watch=true diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index a964cb7d6a..59980c0e3e 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -77,6 +77,10 @@ • Servers matching %s are allowed. • Servers matching IP literals are allowed. • Servers matching IP literals are banned. + %1$s pinned a message. + %1$s unpinned a message. + You pinned a message. + You unpinned a message. %s changed the server ACLs for this room. You changed the server ACLs for this room. @@ -373,7 +377,7 @@ Are you sure you want to sign out? Voice Call Video Call - Open Pinned Messages + Open Pinned Messages View Threads Mark all as read Quick reply @@ -803,11 +807,9 @@ Your homeserver does not currently support threads, so this feature may be unreliable. Some threaded messages may not be reliably available. %sDo you want to enable threads anyway? - Pin - Unpin - Pinned Messages - %1$s pinned a message. - %1$s unpinned a message. + Pin + Unpin + Pinned Messages Search @@ -3039,7 +3041,7 @@ Auto Report Decryption Errors. Your system will automatically send logs when an unable to decrypt error occurs - Enable Pinned Messages + Enable Pinned Messages Enable Thread Messages Note: app will be restarted Show latest user info diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index d5c0ad46c5..6fe608fd6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -449,10 +449,10 @@ fun Event.supportsNotification() = fun Event.isContentReportable() = this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values -fun Event.getIdsOfPinnedEvents(): MutableList? { +fun Event.getIdsOfPinnedEvents(): List? { return getClearContent()?.toModel()?.eventIds } -fun Event.getPreviousIdsOfPinnedEvents(): MutableList? { +fun Event.getPreviousIdsOfPinnedEvents(): List? { return resolvedPrevContent()?.toModel()?.eventIds } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/pinnedmessages/PinnedEventsStateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/pinnedmessages/PinnedEventsStateContent.kt index 0475ee0fc4..646cf62cda 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/pinnedmessages/PinnedEventsStateContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/pinnedmessages/PinnedEventsStateContent.kt @@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class PinnedEventsStateContent( - @Json(name = "pinned") val eventIds: MutableList + @Json(name = "pinned") val eventIds: List ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 2b008ab732..851dea8b9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -67,9 +67,14 @@ interface StateService { suspend fun deleteAvatar() /** - * Pin a message of the room. + * Pin an event of the room. */ - suspend fun pinMessage(eventIds: MutableList) + suspend fun pinEvent(eventId: String) + + /** + * Unpin an event of the room. + */ + suspend fun unpinEvent(eventId: String) /** * Send a state event to the room. @@ -108,16 +113,6 @@ interface StateService { */ fun getStateEventsLive(eventTypes: Set, stateKey: QueryStateEventValue): LiveData> - /** - * Get state event containing the IDs of pinned events of the room - */ - fun getPinnedEventsState(): Event? - - /** - * Tells if an event is a pinned message - */ - fun isPinned(eventId: String): Boolean? - suspend fun setJoinRulePublic() suspend fun setJoinRuleInviteOnly() suspend fun setJoinRuleRestricted(allowList: List) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 2e9b87b797..f49bae1b9b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -43,7 +43,7 @@ interface Timeline { /** * This must be called before any other method after creating the timeline. It ensures the underlying database is open */ - fun start(rootThreadEventId: String? = null, rootPinnedMessageEventId: String? = null) + fun start(rootThreadEventId: String? = null, isFromPinnedEventsTimeline: Boolean = false) /** * This must be called when you don't need the timeline. It ensures the underlying database get closed. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt index 3d9f4e3dc7..64c6a8f068 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt @@ -33,9 +33,9 @@ data class TimelineSettings( */ val rootThreadEventId: String? = null, /** - * The root pinned message eventId if this is a pinned messages timeline, or null if this is NOT a pinned messages timeline. + * True if the timeline is a pinned messages timeline. */ - val rootPinnedMessageEventId: String? = null, + val isFromPinnedEventsTimeline: Boolean = false, /** * If true Sender Info shown in room will get the latest data information (avatar + displayName). */ @@ -50,5 +50,5 @@ data class TimelineSettings( /** * Returns true if this is a pinned messages timeline or false otherwise. */ - fun isPinnedMessagesTimeline() = rootPinnedMessageEventId != null + fun isPinnedEventsTimeline() = isFromPinnedEventsTimeline } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index bf0f482c13..e80c860eb0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -33,7 +33,6 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMembersRespon import org.matrix.android.sdk.internal.session.room.membership.admin.UserIdAndReason import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBody import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody -import org.matrix.android.sdk.internal.session.room.pinnedmessages.PinnedEventsStateResponse import org.matrix.android.sdk.internal.session.room.read.ReadBody import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody @@ -249,7 +248,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("eventType") eventType: String, @Path("state_key") stateKey: String - ): PinnedEventsStateResponse + ): Content /** * Paginate relations for event based in normal topological order. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/pinnedmessages/PinnedEventsStateResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/pinnedmessages/PinnedEventsStateResponse.kt deleted file mode 100644 index c964f1c769..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/pinnedmessages/PinnedEventsStateResponse.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.session.room.pinnedmessages - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class PinnedEventsStateResponse( - /** - * A unique identifier for the event. - */ - @Json(name = "pinned") val pinned: List -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 51cf975574..7a12bf8896 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -173,7 +173,25 @@ internal class DefaultStateService @AssistedInject constructor( ) } - override suspend fun pinMessage(eventIds: MutableList) { + override suspend fun pinEvent(eventId: String) { + val pinnedEvents = getStateEvent(EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals("")) + ?.getIdsOfPinnedEvents() + ?.toMutableList() + pinnedEvents?.add(eventId) + val newListOfPinnedEvents = pinnedEvents?.toList() ?: return + setPinnedEvents(newListOfPinnedEvents) + } + + override suspend fun unpinEvent(eventId: String) { + val pinnedEvents = getStateEvent(EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals("")) + ?.getIdsOfPinnedEvents() + ?.toMutableList() + pinnedEvents?.remove(eventId) + val newListOfPinnedEvents = pinnedEvents?.toList() ?: return + setPinnedEvents(newListOfPinnedEvents) + } + + private suspend fun setPinnedEvents(eventIds: List) { sendStateEvent( eventType = EventType.STATE_ROOM_PINNED_EVENT, body = PinnedEventsStateContent(eventIds).toContent(), @@ -181,15 +199,6 @@ internal class DefaultStateService @AssistedInject constructor( ) } - override fun getPinnedEventsState(): Event? { - return getStateEvent(EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals("")) - } - - override fun isPinned(eventId: String): Boolean? { - val idsOfPinnedEvents: MutableList = getPinnedEventsState()?.getIdsOfPinnedEvents() ?: return null - return idsOfPinnedEvents.contains(eventId) - } - override suspend fun setJoinRulePublic() { updateJoinRule(RoomJoinRules.PUBLIC, null) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 458d2b8af0..469c387ccc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -99,8 +99,7 @@ internal class DefaultTimeline( private var isFromThreadTimeline = false private var rootThreadEventId: String? = null - private var isFromPinnedMessagesTimeline = false - private var rootPinnedMessageEventId: String? = null + private var isFromPinnedEventsTimeline = false private val strategyDependencies = LoadTimelineStrategy.Dependencies( timelineSettings = settings, @@ -132,7 +131,7 @@ internal class DefaultTimeline( override fun addListener(listener: Timeline.Listener): Boolean { listeners.add(listener) timelineScope.launch { - val snapshot = if (isFromPinnedMessagesTimeline) { + val snapshot = if (isFromPinnedEventsTimeline) { getPinnedEvents() } else { strategy.buildSnapshot() @@ -152,7 +151,7 @@ internal class DefaultTimeline( listeners.clear() } - override fun start(rootThreadEventId: String?, rootPinnedMessageEventId: String?) { + override fun start(rootThreadEventId: String?, isFromPinnedEventsTimeline: Boolean) { timelineScope.launch { loadRoomMembersIfNeeded() } @@ -161,8 +160,7 @@ internal class DefaultTimeline( if (isStarted.compareAndSet(false, true)) { isFromThreadTimeline = rootThreadEventId != null this@DefaultTimeline.rootThreadEventId = rootThreadEventId - isFromPinnedMessagesTimeline = rootPinnedMessageEventId != null - this@DefaultTimeline.rootPinnedMessageEventId = rootPinnedMessageEventId + this@DefaultTimeline.isFromPinnedEventsTimeline = isFromPinnedEventsTimeline // / val realm = Realm.getInstance(realmConfiguration) ensureReadReceiptAreLoaded(realm) @@ -267,8 +265,8 @@ internal class DefaultTimeline( } } Timber.v("$baseLogMessage: result $loadMoreResult") - val hasMoreToLoad = if (isFromPinnedMessagesTimeline) { - !areAllPinnedMessagesLoaded() + val hasMoreToLoad = if (isFromPinnedEventsTimeline) { + !areAllPinnedEventsLoaded() } else { loadMoreResult != LoadMoreResult.REACHED_END } @@ -352,7 +350,7 @@ internal class DefaultTimeline( } private suspend fun postSnapshot() { - val snapshot = if (isFromPinnedMessagesTimeline) { + val snapshot = if (isFromPinnedEventsTimeline) { getPinnedEvents() } else { strategy.buildSnapshot() @@ -371,25 +369,22 @@ internal class DefaultTimeline( } } - private fun getIdsOfPinnedEvents(): MutableList { + private fun getIdsOfPinnedEvents(): List { return stateEventDataSource .getStateEvent(roomId, EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals("")) - ?.getIdsOfPinnedEvents() ?: mutableListOf("") + ?.getIdsOfPinnedEvents() + .orEmpty() } private fun getPinnedEvents(): List { - val idsOfPinnedEvents = getIdsOfPinnedEvents() - val pinnedEvents = ArrayList() - for (id in idsOfPinnedEvents) { - val timelineEvent = timelineEventDataSource.getTimelineEvent(roomId, id) - if (timelineEvent != null) { - pinnedEvents.add(timelineEvent) - } - } - return pinnedEvents.reversed() + return getIdsOfPinnedEvents() + .mapNotNull { id -> + timelineEventDataSource.getTimelineEvent(roomId, id) + } + .reversed() } - private fun areAllPinnedMessagesLoaded(): Boolean { + private fun areAllPinnedEventsLoaded(): Boolean { return getIdsOfPinnedEvents().size == getPinnedEvents().size } diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index caa60af797..f32b1fd95b 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -39,7 +39,7 @@ true true - false + false false true false diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 9d62904fc4..60c072c8ad 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -149,7 +149,7 @@ - + , val compressBeforeSending: Boolean) : RoomDetailAction() data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index b60154192e..1e4aafc255 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -71,7 +71,6 @@ data class RoomDetailViewState( val isAllowedToManageWidgets: Boolean = false, val isAllowedToStartWebRTCCall: Boolean = true, val isAllowedToSetupEncryption: Boolean = true, - val rootPinnedMessageEventId: String?, val hasFailedSending: Boolean = false, val jitsiState: JitsiState = JitsiState(), val switchToParentSpace: Boolean = false, @@ -81,6 +80,7 @@ data class RoomDetailViewState( val isSharingLiveLocation: Boolean = false, val showKeyboardWhenPresented: Boolean = false, val sharedData: SharedData? = null, + val isFromPinnedEventsTimeline: Boolean = false, ) : MavericksState { constructor(args: TimelineArgs) : this( @@ -93,7 +93,7 @@ data class RoomDetailViewState( rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId, showKeyboardWhenPresented = args.threadTimelineArgs?.showKeyboard.orFalse(), sharedData = args.sharedData, - rootPinnedMessageEventId = args.pinnedMessagesTimelineArgs?.rootPinnedMessageEventId, + isFromPinnedEventsTimeline = args.pinnedEventsTimelineArgs != null, ) fun isCallOptionAvailable(): Boolean { @@ -115,7 +115,7 @@ data class RoomDetailViewState( fun isThreadTimeline() = rootThreadEventId != null - fun isPinnedMessagesTimeline() = rootPinnedMessageEventId != null + fun isPinnedEventsTimeline() = isFromPinnedEventsTimeline fun isLocalRoom() = RoomLocalEcho.isLocalEchoId(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 90b9929762..14f7a2ca93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -159,7 +159,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet -import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedMessagesTimelineArgs +import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedEventsTimelineArgs import im.vector.app.features.home.room.threads.ThreadsManager import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.html.EventHtmlRenderer @@ -379,9 +379,8 @@ class TimelineFragment : ) } - if (isPinnedMessagesTimeline()) { - views.composerContainer.isVisible = false - views.voiceMessageRecorderContainer.isVisible = false + if (isPinnedEventsTimeline()) { + views.hideComposerViews() } timelineViewModel.observeViewEvents { @@ -883,8 +882,8 @@ class TimelineFragment : callActionsHandler.onVideoCallClicked() true } - R.id.open_pinned_messages -> { - navigateToPinnedMessages() + R.id.open_pinned_events -> { + navigateToPinnedEvents() true } R.id.menu_timeline_thread_list -> { @@ -1116,7 +1115,7 @@ class TimelineFragment : } private fun updateJumpToReadMarkerViewVisibility() { - if (isThreadTimeLine() || isPinnedMessagesTimeline()) return + if (isThreadTimeLine() || isPinnedEventsTimeline()) return viewLifecycleOwner.lifecycleScope.launchWhenResumed { val state = timelineViewModel.awaitState() val showJumpToUnreadBanner = when (state.unreadState) { @@ -1197,6 +1196,9 @@ class TimelineFragment : vectorBaseActivity.finish() } updateLiveLocationIndicator(mainState.isSharingLiveLocation) + if (isPinnedEventsTimeline()) { + views.hideComposerViews() + } } private fun handleRoomSummaryFailure(asyncRoomSummary: Fail) { @@ -1245,16 +1247,18 @@ class TimelineFragment : } views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title) } - isPinnedMessagesTimeline() -> { + isPinnedEventsTimeline() -> { + withState(timelineViewModel) { state -> + timelineArgs.let { + val matrixItem = MatrixItem.RoomItem(it.roomId, state.asyncRoomSummary()?.displayName, state.asyncRoomSummary()?.avatarUrl) + avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView) + views.includeThreadToolbar.roomToolbarThreadShieldImageView.render(state.asyncRoomSummary()?.roomEncryptionTrustLevel) + views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = state.asyncRoomSummary()?.displayName + } + } views.includeRoomToolbar.roomToolbarContentView.isVisible = false views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = true - timelineArgs.pinnedMessagesTimelineArgs?.let { - val matrixItem = MatrixItem.RoomItem(it.roomId, it.displayName, it.avatarUrl) - avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView) - views.includeThreadToolbar.roomToolbarThreadShieldImageView.render(it.roomEncryptionTrustLevel) - views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = it.displayName - } - views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.pinned_messages_timeline_title) + views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.pinned_events_timeline_title) } else -> { views.includeRoomToolbar.roomToolbarContentView.isVisible = true @@ -1564,7 +1568,7 @@ class TimelineFragment : this.view?.hideKeyboard() MessageActionsBottomSheet - .newInstance(roomId, informationData, isThreadTimeLine(), isPinnedMessagesTimeline()) + .newInstance(roomId, informationData, isThreadTimeLine(), isPinnedEventsTimeline()) .show(requireActivity().supportFragmentManager, "MESSAGE_CONTEXTUAL_ACTIONS") return true @@ -1816,13 +1820,13 @@ class TimelineFragment : requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } - is EventSharedAction.PinMessage -> { - timelineViewModel.handle(RoomDetailAction.PinMessage(action.eventId)) + is EventSharedAction.PinEvent -> { + timelineViewModel.handle(RoomDetailAction.PinEvent(action.eventId)) } - is EventSharedAction.UnpinMessage -> { - timelineViewModel.handle(RoomDetailAction.UnpinMessage(action.eventId)) + is EventSharedAction.UnpinEvent -> { + timelineViewModel.handle(RoomDetailAction.UnpinEvent(action.eventId)) } - is EventSharedAction.ViewPinnedMessageInRoom -> { + is EventSharedAction.ViewPinnedEventInRoom -> { handleViewInRoomAction(action.eventId) } is EventSharedAction.ReplyInThread -> { @@ -2005,24 +2009,19 @@ class TimelineFragment : } /** - * Navigate to pinned messages for the current room using the PinnedMessagesActivity. + * Navigate to pinned events for the current room using the PinnedEventsActivity. */ - private fun navigateToPinnedMessages() = withState(timelineViewModel) { state -> - val pinnedEventId = timelineViewModel.getIdOfLastPinnedEvent() + private fun navigateToPinnedEvents() { context?.let { - val pinnedMessagesTimelineArgs = PinnedMessagesTimelineArgs( + val pinnedEventsTimelineArgs = PinnedEventsTimelineArgs( roomId = timelineArgs.roomId, - displayName = state.asyncRoomSummary()?.displayName, - roomEncryptionTrustLevel = state.asyncRoomSummary()?.roomEncryptionTrustLevel, - avatarUrl = state.asyncRoomSummary()?.avatarUrl, - rootPinnedMessageEventId = pinnedEventId ) - navigator.openPinnedMessages(it, pinnedMessagesTimelineArgs) + navigator.openPinnedEvents(it, pinnedEventsTimelineArgs) } } private fun handleViewInRoomAction(eventId: String) { - val newRoom = timelineArgs.copy(threadTimelineArgs = null, pinnedMessagesTimelineArgs = null, eventId = eventId) + val newRoom = timelineArgs.copy(threadTimelineArgs = null, pinnedEventsTimelineArgs = null, eventId = eventId) context?.let { con -> val intent = RoomDetailActivity.newIntent(con, newRoom, false) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK @@ -2086,7 +2085,7 @@ class TimelineFragment : /** * Returns true if the current room is a Pinned Messages room, false otherwise. */ - private fun isPinnedMessagesTimeline(): Boolean = withState(timelineViewModel) { it.isPinnedMessagesTimeline() } + private fun isPinnedEventsTimeline(): Boolean = withState(timelineViewModel) { it.isPinnedEventsTimeline() } /** * Returns true if the current room is a local room, false otherwise. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 8044845301..3dc3839051 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -204,10 +204,10 @@ class TimelineViewModel @AssistedInject constructor( } private fun initSafe(room: Room, timeline: Timeline) { - timeline.start(initialState.rootThreadEventId, initialState.rootPinnedMessageEventId) + timeline.start(initialState.rootThreadEventId, initialState.isFromPinnedEventsTimeline) timeline.addListener(this) observeMembershipChanges() - if (!initialState.isPinnedMessagesTimeline()) { + if (!initialState.isPinnedEventsTimeline()) { observeSummaryState() } getUnreadState() @@ -451,8 +451,8 @@ class TimelineViewModel @AssistedInject constructor( override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.PinMessage -> handlePinMessage(action) - is RoomDetailAction.UnpinMessage -> handleUnpinMessage(action) + is RoomDetailAction.PinEvent -> handlePinEvent(action) + is RoomDetailAction.UnpinEvent -> handleUnpinEvent(action) is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action) is RoomDetailAction.SendMedia -> handleSendMedia(action) is RoomDetailAction.SendSticker -> handleSendSticker(action) @@ -762,14 +762,6 @@ class TimelineViewModel @AssistedInject constructor( return room?.membershipService()?.getRoomMember(userId) } - fun getIdOfLastPinnedEvent(): String? { - return room - ?.stateService() - ?.getPinnedEventsState() - ?.getIdsOfPinnedEvents() - ?.last() - } - private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) { if (room == null) return // Ensure outbound session keys @@ -840,7 +832,7 @@ class TimelineViewModel @AssistedInject constructor( else -> false } } - initialState.isPinnedMessagesTimeline() -> false + initialState.isPinnedEventsTimeline() -> false else -> { when (itemId) { R.id.timeline_setting -> true @@ -851,7 +843,7 @@ class TimelineViewModel @AssistedInject constructor( // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ R.id.join_conference -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined R.id.search -> state.isSearchAvailable() - R.id.open_pinned_messages -> vectorPreferences.arePinnedMessagesEnabled() && areTherePinnedMessages() + R.id.open_pinned_events -> vectorPreferences.arePinnedEventsEnabled() && areTherePinnedEvents() R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled() R.id.dev_tools -> vectorPreferences.developerMode() else -> false @@ -1038,40 +1030,12 @@ class TimelineViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId)) } - private fun handlePinMessage(action: RoomDetailAction.PinMessage) { - if (room == null) return - val idsOfPinnedMessages = getIdsOfPinnedEvents() - if (idsOfPinnedMessages == null) return - idsOfPinnedMessages.add(action.eventId) - sendPinnedStateEvent(idsOfPinnedMessages, action) - } - - private fun handleUnpinMessage(action: RoomDetailAction.UnpinMessage) { - if (room == null) return - val idsOfPinnedMessages = getIdsOfPinnedEvents() - if (idsOfPinnedMessages == null) return - idsOfPinnedMessages.remove(action.eventId) - sendPinnedStateEvent(idsOfPinnedMessages, action) - } - - private fun getIdsOfPinnedEvents(): MutableList? { - return room - ?.stateService() - ?.getPinnedEventsState() - ?.getIdsOfPinnedEvents() - } - - private fun areTherePinnedMessages(): Boolean { - val idsOfPinnedMessages = getIdsOfPinnedEvents() ?: return false - return idsOfPinnedMessages.isNotEmpty() - } - - private fun sendPinnedStateEvent(eventIds: MutableList, action: RoomDetailAction) { + private fun handlePinEvent(action: RoomDetailAction.PinEvent) { viewModelScope.launch(Dispatchers.IO) { try { room ?.stateService() - ?.pinMessage(eventIds) + ?.pinEvent(action.eventId) _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) @@ -1079,6 +1043,31 @@ class TimelineViewModel @AssistedInject constructor( } } + private fun handleUnpinEvent(action: RoomDetailAction.UnpinEvent) { + viewModelScope.launch(Dispatchers.IO) { + try { + room + ?.stateService() + ?.unpinEvent(action.eventId) + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) + } + } + } + + private fun getIdsOfPinnedEvents(): List? { + return room + ?.stateService() + ?.getStateEvent(EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals("")) + ?.getIdsOfPinnedEvents() + } + + private fun areTherePinnedEvents(): Boolean { + val idsOfPinnedEvents = getIdsOfPinnedEvents() ?: return false + return idsOfPinnedEvents.isNotEmpty() + } + private fun handleResendEvent(action: RoomDetailAction.ResendMessage) { if (room == null) return val targetEventId = action.eventId diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt index d71f8ae7a9..ba99331a00 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.arguments import android.os.Parcelable -import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedMessagesTimelineArgs +import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedEventsTimelineArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.share.SharedData import kotlinx.parcelize.Parcelize @@ -29,7 +29,7 @@ data class TimelineArgs( val sharedData: SharedData? = null, val openShareSpaceForId: String? = null, val threadTimelineArgs: ThreadTimelineArgs? = null, - val pinnedMessagesTimelineArgs: PinnedMessagesTimelineArgs? = null, + val pinnedEventsTimelineArgs: PinnedEventsTimelineArgs? = null, val switchToParentSpace: Boolean = false, val isInviteAlreadyAccepted: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index 1e7b7c99ef..6679aa1d08 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -53,13 +53,13 @@ sealed class EventSharedAction( data class ReplyInThread(val eventId: String, val startsThread: Boolean) : EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread) - data class PinMessage(val eventId: String) : - EventSharedAction(R.string.pinning_message, R.drawable.ic_pin_message) + data class PinEvent(val eventId: String) : + EventSharedAction(R.string.pinning_event, R.drawable.ic_pin_event) - data class UnpinMessage(val eventId: String) : - EventSharedAction(R.string.unpinning_message, R.drawable.ic_unpin_message) + data class UnpinEvent(val eventId: String) : + EventSharedAction(R.string.unpinning_event, R.drawable.ic_unpin_event) - data class ViewPinnedMessageInRoom(val eventId: String) : + data class ViewPinnedEventInRoom(val eventId: String) : EventSharedAction(R.string.view_in_room, R.drawable.ic_threads_view_in_room_24) object ViewInRoom : diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt index fa03877219..9d76410129 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt @@ -36,7 +36,7 @@ data class ActionPermissions( val canSendMessage: Boolean = false, val canReact: Boolean = false, val canRedact: Boolean = false, - val canPinMessage: Boolean = false + val canPinEvent: Boolean = false ) data class MessageActionState( @@ -52,7 +52,7 @@ data class MessageActionState( val expendedReportContentMenu: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions(), val isFromThreadTimeline: Boolean = false, - val isFromPinnedMessagesTimeline: Boolean = false + val isFromPinnedEventsTimeline: Boolean = false ) : MavericksState { constructor(args: TimelineEventFragmentArgs) : this( @@ -60,7 +60,7 @@ data class MessageActionState( eventId = args.eventId, informationData = args.informationData, isFromThreadTimeline = args.isFromThreadTimeline, - isFromPinnedMessagesTimeline = args.isFromPinnedMessagesTimeline + isFromPinnedEventsTimeline = args.isFromPinnedEventsTimeline ) fun senderName(): String = informationData.memberName?.toString() ?: "" diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index c0740f0905..9f6a117dfd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -93,7 +93,7 @@ class MessageActionsBottomSheet : } companion object { - fun newInstance(roomId: String, informationData: MessageInformationData, isFromThreadTimeline: Boolean, isFromPinnedMessagesTimeline: Boolean): MessageActionsBottomSheet { + fun newInstance(roomId: String, informationData: MessageInformationData, isFromThreadTimeline: Boolean, isFromPinnedEventsTimeline: Boolean): MessageActionsBottomSheet { return MessageActionsBottomSheet().apply { setArguments( TimelineEventFragmentArgs( @@ -101,7 +101,7 @@ class MessageActionsBottomSheet : roomId, informationData, isFromThreadTimeline, - isFromPinnedMessagesTimeline + isFromPinnedEventsTimeline ) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 341fa83987..feb7e7a444 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -42,9 +42,11 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.getIdsOfPinnedEvents import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isContentReportable import org.matrix.android.sdk.api.session.events.model.isTextMessage @@ -131,8 +133,8 @@ class MessageActionsViewModel @AssistedInject constructor( val canReact = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) val canRedact = powerLevelsHelper.isUserAbleToRedact(session.myUserId) val canSendMessage = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) - val canPinMessage = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.STATE_ROOM_PINNED_EVENT) - val permissions = ActionPermissions(canSendMessage = canSendMessage, canRedact = canRedact, canReact = canReact, canPinMessage = canPinMessage) + val canPinEvent = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_PINNED_EVENT) + val permissions = ActionPermissions(canSendMessage = canSendMessage, canRedact = canRedact, canReact = canReact, canPinEvent = canPinEvent) setState { copy(actionPermissions = permissions) } @@ -334,91 +336,95 @@ class MessageActionsViewModel @AssistedInject constructor( ) { val eventId = timelineEvent.eventId if (!timelineEvent.root.isRedacted()) { - if (initialState.isFromPinnedMessagesTimeline) { - if (actionPermissions.canPinMessage && vectorPreferences.arePinnedMessagesEnabled()) { - add(EventSharedAction.UnpinMessage(eventId)) - add(EventSharedAction.ViewPinnedMessageInRoom(eventId)) + if (initialState.isFromPinnedEventsTimeline && vectorPreferences.arePinnedEventsEnabled()) { + add(EventSharedAction.ViewPinnedEventInRoom(eventId)) + if (actionPermissions.canPinEvent) { + add(EventSharedAction.UnpinEvent(eventId)) } - return - } - if (canReply(timelineEvent, messageContent, actionPermissions)) { - add(EventSharedAction.Reply(eventId)) - } - - if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) { - add(EventSharedAction.ReplyInThread(eventId, !timelineEvent.isRootThread())) - } - - if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) { - add(EventSharedAction.ViewInRoom) - } - - if (canEndPoll(timelineEvent, actionPermissions)) { - add(EventSharedAction.EndPoll(timelineEvent.eventId)) - } - - if (canEdit(timelineEvent, session.myUserId, actionPermissions)) { - add(EventSharedAction.Edit(eventId, timelineEvent.root.getClearType())) - } - - if (canCopy(msgType)) { - // TODO copy images? html? see ClipBoard - add(EventSharedAction.Copy(messageContent!!.body)) - } - - if (timelineEvent.canReact() && actionPermissions.canReact) { - add(EventSharedAction.AddReaction(eventId)) - } - - if (actionPermissions.canPinMessage && vectorPreferences.arePinnedMessagesEnabled()) { - val id: String = timelineEvent.root.eventId ?: return - val isPinned: Boolean = room?.stateService()?.isPinned(id) ?: return - if (isPinned) { - add(EventSharedAction.UnpinMessage(eventId)) - } else { - add(EventSharedAction.PinMessage(eventId)) + } else { + if (canReply(timelineEvent, messageContent, actionPermissions)) { + add(EventSharedAction.Reply(eventId)) } - } - if (canViewReactions(timelineEvent)) { - add(EventSharedAction.ViewReactions(informationData)) - } + if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) { + add(EventSharedAction.ReplyInThread(eventId, !timelineEvent.isRootThread())) + } - if (canQuote(timelineEvent, messageContent, actionPermissions)) { - add(EventSharedAction.Quote(eventId)) - } + if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) { + add(EventSharedAction.ViewInRoom) + } - if (timelineEvent.hasBeenEdited()) { - add(EventSharedAction.ViewEditHistory(informationData)) - } + if (canEndPoll(timelineEvent, actionPermissions)) { + add(EventSharedAction.EndPoll(timelineEvent.eventId)) + } - if (canSave(msgType) && messageContent is MessageWithAttachmentContent) { - add(EventSharedAction.Save(timelineEvent.eventId, messageContent)) - } + if (canEdit(timelineEvent, session.myUserId, actionPermissions)) { + add(EventSharedAction.Edit(eventId, timelineEvent.root.getClearType())) + } - if (canShare(msgType)) { - add(EventSharedAction.Share(timelineEvent.eventId, messageContent!!)) - } + if (canCopy(msgType)) { + // TODO copy images? html? see ClipBoard + add(EventSharedAction.Copy(messageContent!!.body)) + } - if (canRedact(timelineEvent, actionPermissions)) { - if (timelineEvent.root.getClearType() in EventType.POLL_START.values) { - add( - EventSharedAction.Redact( - eventId, - askForReason = informationData.senderId != session.myUserId, - dialogTitleRes = R.string.delete_poll_dialog_title, - dialogDescriptionRes = R.string.delete_poll_dialog_content - ) - ) - } else { - add( - EventSharedAction.Redact( - eventId, - askForReason = informationData.senderId != session.myUserId, - dialogTitleRes = R.string.delete_event_dialog_title, - dialogDescriptionRes = R.string.delete_event_dialog_content - ) - ) + if (timelineEvent.canReact() && actionPermissions.canReact) { + add(EventSharedAction.AddReaction(eventId)) + } + + if (actionPermissions.canPinEvent && vectorPreferences.arePinnedEventsEnabled()) { + val isPinned = room + ?.stateService() + ?.getStateEvent(EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals("")) + ?.getIdsOfPinnedEvents() + ?.contains(eventId) + .orFalse() + if (isPinned) { + add(EventSharedAction.UnpinEvent(eventId)) + } else { + add(EventSharedAction.PinEvent(eventId)) + } + } + + if (canViewReactions(timelineEvent)) { + add(EventSharedAction.ViewReactions(informationData)) + } + + if (canQuote(timelineEvent, messageContent, actionPermissions)) { + add(EventSharedAction.Quote(eventId)) + } + + if (timelineEvent.hasBeenEdited()) { + add(EventSharedAction.ViewEditHistory(informationData)) + } + + if (canSave(msgType) && messageContent is MessageWithAttachmentContent) { + add(EventSharedAction.Save(timelineEvent.eventId, messageContent)) + } + + if (canShare(msgType)) { + add(EventSharedAction.Share(timelineEvent.eventId, messageContent!!)) + } + + if (canRedact(timelineEvent, actionPermissions)) { + if (timelineEvent.root.getClearType() in EventType.POLL_START.values) { + add( + EventSharedAction.Redact( + eventId, + askForReason = informationData.senderId != session.myUserId, + dialogTitleRes = R.string.delete_poll_dialog_title, + dialogDescriptionRes = R.string.delete_poll_dialog_content + ) + ) + } else { + add( + EventSharedAction.Redact( + eventId, + askForReason = informationData.senderId != session.myUserId, + dialogTitleRes = R.string.delete_event_dialog_title, + dialogDescriptionRes = R.string.delete_event_dialog_content + ) + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt index 17d55ac8b9..e6d14bdc7f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt @@ -26,5 +26,5 @@ data class TimelineEventFragmentArgs( val roomId: String, val informationData: MessageInformationData, val isFromThreadTimeline: Boolean = false, - val isFromPinnedMessagesTimeline: Boolean = false + val isFromPinnedEventsTimeline: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index e2956d9d98..efc139a7af 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -122,16 +122,24 @@ class NoticeEventFormatter @Inject constructor( } private fun formatPinnedEvent(event: Event, disambiguatedDisplayName: String): CharSequence? { - val idsOfPinnedEvents: MutableList = event.getIdsOfPinnedEvents() ?: return null - val previousIdsOfPinnedEvents: MutableList? = event.getPreviousIdsOfPinnedEvents() - // A message was pinned - val pinnedMessageString = if (event.resolvedPrevContent() == null || previousIdsOfPinnedEvents != null && previousIdsOfPinnedEvents.size < idsOfPinnedEvents.size) { - sp.getString(R.string.user_pinned_message, disambiguatedDisplayName) - // A message was unpinned + val idsOfPinnedEvents: List = event.getIdsOfPinnedEvents() ?: return null + val previousIdsOfPinnedEvents: List? = event.getPreviousIdsOfPinnedEvents() + // An event was pinned + val pinnedEventString = if (event.resolvedPrevContent() == null || previousIdsOfPinnedEvents != null && previousIdsOfPinnedEvents.size < idsOfPinnedEvents.size) { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_user_pinned_event_by_you, disambiguatedDisplayName) + } else { + sp.getString(R.string.notice_user_pinned_event, disambiguatedDisplayName) + } + // An event was unpinned } else { - sp.getString(R.string.user_unpinned_message, disambiguatedDisplayName) + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_user_unpinned_event_by_you, disambiguatedDisplayName) + } else { + sp.getString(R.string.notice_user_unpinned_event, disambiguatedDisplayName) + } } - return pinnedMessageString + return pinnedEventString } private fun formatRoomPowerLevels(event: Event, disambiguatedDisplayName: String): CharSequence? { @@ -194,7 +202,6 @@ class NoticeEventFormatter @Inject constructor( } fun format(event: Event, senderName: String?, isDm: Boolean): CharSequence? { - Timber.v("°°°°°°°°°°°°°°°°°°°format(event: Event, senderName: String?, isDm: Boolean)") return when (val type = event.getClearType()) { EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName, isDm) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName) @@ -889,7 +896,6 @@ class NoticeEventFormatter @Inject constructor( } fun formatRedactedEvent(event: Event): String { - Timber.v("°°°°°°°formatRedactedEvent°°°°°°") return (event .unsignedData ?.redactedEvent diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt index 55d82e9e4f..76ceb4b688 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -113,7 +113,7 @@ class MergedTimelines( secondaryTimeline.removeAllListeners() } - override fun start(rootThreadEventId: String?, rootPinnedMessageEventId: String?) { + override fun start(rootThreadEventId: String?, isFromPinnedEventsTimeline: Boolean) { mainTimeline.start() secondaryTimeline.start() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/pinnedevents/PinnedEventsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/pinnedevents/PinnedEventsActivity.kt new file mode 100644 index 0000000000..ca673a4177 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/pinnedevents/PinnedEventsActivity.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.pinnedmessages + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.replaceFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivityPinnedEventsBinding +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.TimelineFragment +import im.vector.app.features.home.room.detail.arguments.TimelineArgs +import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedEventsTimelineArgs +import im.vector.lib.core.utils.compat.getParcelableCompat +import javax.inject.Inject + +@AndroidEntryPoint +class PinnedEventsActivity : VectorBaseActivity() { + + @Inject lateinit var avatarRenderer: AvatarRenderer + + override fun getBinding() = ActivityPinnedEventsBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initFragment() + } + + private fun initFragment() { + if (isFirstCreation()) { + val args = getPinnedEventsTimelineArgs() + if (args == null) { + finish() + } else { + initPinnedEventsTimelineFragment(args) + } + } + } + + private fun initPinnedEventsTimelineFragment(pinnedEventsTimelineArgs: PinnedEventsTimelineArgs) = + replaceFragment( + views.pinnedEventsActivityFragmentContainer, + TimelineFragment::class.java, + TimelineArgs( + roomId = pinnedEventsTimelineArgs.roomId, + pinnedEventsTimelineArgs = pinnedEventsTimelineArgs + ) + ) + + private fun getPinnedEventsTimelineArgs(): PinnedEventsTimelineArgs? = intent?.extras?.getParcelableCompat(PINNED_EVENTS_TIMELINE_ARGS) + + companion object { + const val PINNED_EVENTS_TIMELINE_ARGS = "PINNED_EVENTS_TIMELINE_ARGS" + + fun newIntent( + context: Context, + pinnedEventsTimelineArgs: PinnedEventsTimelineArgs?, + ): Intent { + return Intent(context, PinnedEventsActivity::class.java).apply { + putExtra(PINNED_EVENTS_TIMELINE_ARGS, pinnedEventsTimelineArgs) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/arguments/PinnedMessagesTimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/pinnedevents/arguments/PinnedEventsTimelineArgs.kt similarity index 70% rename from vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/arguments/PinnedMessagesTimelineArgs.kt rename to vector/src/main/java/im/vector/app/features/home/room/pinnedevents/arguments/PinnedEventsTimelineArgs.kt index daf6bb9240..2c81c2f4d5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/arguments/PinnedMessagesTimelineArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/pinnedevents/arguments/PinnedEventsTimelineArgs.kt @@ -18,13 +18,8 @@ package im.vector.app.features.home.room.pinnedmessages.arguments import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel @Parcelize -data class PinnedMessagesTimelineArgs( - val roomId: String, - val displayName: String?, - val avatarUrl: String?, - val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, - val rootPinnedMessageEventId: String? +data class PinnedEventsTimelineArgs( + val roomId: String ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/PinnedMessagesActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/PinnedMessagesActivity.kt deleted file mode 100644 index 3c5c305a20..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/pinnedmessages/PinnedMessagesActivity.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.home.room.pinnedmessages - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.core.extensions.replaceFragment -import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.databinding.ActivityPinnedMessagesBinding -import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.TimelineFragment -import im.vector.app.features.home.room.detail.arguments.TimelineArgs -import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedMessagesTimelineArgs -import im.vector.lib.core.utils.compat.getParcelableCompat -import javax.inject.Inject - -@AndroidEntryPoint -class PinnedMessagesActivity : VectorBaseActivity() { - - @Inject lateinit var avatarRenderer: AvatarRenderer - - override fun getBinding() = ActivityPinnedMessagesBinding.inflate(layoutInflater) - - override fun getCoordinatorLayout() = views.coordinatorLayout - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initFragment() - } - - private fun initFragment() { - if (isFirstCreation()) { - when (val fragment = fragmentToNavigate()) { - is DisplayFragment.PinnedMessagesTimeLine -> { - initPinnedMessagesTimelineFragment(fragment.pinnedMessagesTimelineArgs) - } - is DisplayFragment.ErrorFragment -> { - finish() - } - } - } - } - - private fun initPinnedMessagesTimelineFragment(pinnedMessagesTimelineArgs: PinnedMessagesTimelineArgs) = - replaceFragment( - views.pinnedMessagesActivityFragmentContainer, - TimelineFragment::class.java, - TimelineArgs( - roomId = pinnedMessagesTimelineArgs.roomId, - pinnedMessagesTimelineArgs = pinnedMessagesTimelineArgs - ) - ) - - /** - * Determine in witch fragment we should navigate. - */ - private fun fragmentToNavigate(): DisplayFragment { - getPinnedMessagesTimelineArgs()?.let { - return DisplayFragment.PinnedMessagesTimeLine(it) - } - return DisplayFragment.ErrorFragment - } - - private fun getPinnedMessagesTimelineArgs(): PinnedMessagesTimelineArgs? = intent?.extras?.getParcelableCompat(PINNED_MESSAGES_TIMELINE_ARGS) - - companion object { - const val PINNED_MESSAGES_TIMELINE_ARGS = "PINNED_MESSAGES_TIMELINE_ARGS" - - fun newIntent( - context: Context, - pinnedMessagesTimelineArgs: PinnedMessagesTimelineArgs?, - ): Intent { - return Intent(context, PinnedMessagesActivity::class.java).apply { - putExtra(PINNED_MESSAGES_TIMELINE_ARGS, pinnedMessagesTimelineArgs) - } - } - } - - sealed class DisplayFragment { - data class PinnedMessagesTimeLine(val pinnedMessagesTimelineArgs: PinnedMessagesTimelineArgs) : DisplayFragment() - object ErrorFragment : DisplayFragment() - } -} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 58a28386fd..7333154776 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -58,8 +58,8 @@ import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.search.SearchActivity import im.vector.app.features.home.room.detail.search.SearchArgs import im.vector.app.features.home.room.filtered.FilteredRoomsActivity -import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedMessagesTimelineArgs -import im.vector.app.features.home.room.pinnedmessages.PinnedMessagesActivity +import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedEventsTimelineArgs +import im.vector.app.features.home.room.pinnedmessages.PinnedEventsActivity import im.vector.app.features.home.room.threads.ThreadsActivity import im.vector.app.features.home.room.threads.arguments.ThreadListArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs @@ -601,11 +601,11 @@ class DefaultNavigator @Inject constructor( ) } - override fun openPinnedMessages(context: Context, pinnedMessagesTimelineArgs: PinnedMessagesTimelineArgs) { + override fun openPinnedEvents(context: Context, pinnedEventsTimelineArgs: PinnedEventsTimelineArgs) { context.startActivity( - PinnedMessagesActivity.newIntent( + PinnedEventsActivity.newIntent( context = context, - pinnedMessagesTimelineArgs = pinnedMessagesTimelineArgs + pinnedEventsTimelineArgs = pinnedEventsTimelineArgs ) ) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 6cba3a298f..a7f736549d 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -27,7 +27,7 @@ import androidx.fragment.app.FragmentActivity import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.displayname.getBestName -import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedMessagesTimelineArgs +import im.vector.app.features.home.room.pinnedmessages.arguments.PinnedEventsTimelineArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationSharingMode @@ -199,7 +199,7 @@ interface Navigator { fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) - fun openPinnedMessages(context: Context, pinnedMessagesTimelineArgs: PinnedMessagesTimelineArgs) + fun openPinnedEvents(context: Context, pinnedEventsTimelineArgs: PinnedEventsTimelineArgs) fun openScreenSharingPermissionDialog( screenCaptureIntent: Intent, diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 0278ff91c2..0f9b2c03f0 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -234,7 +234,7 @@ class VectorPreferences @Inject constructor( private const val SETTINGS_LABS_ENABLE_ELEMENT_CALL_PERMISSION_SHORTCUTS = "SETTINGS_LABS_ENABLE_ELEMENT_CALL_PERMISSION_SHORTCUTS" - private const val SETTINGS_LABS_ENABLE_PINNED_MESSAGES = "SETTINGS_LABS_ENABLE_PINNED_MESSAGES" + private const val SETTINGS_LABS_ENABLE_PINNED_EVENTS = "SETTINGS_LABS_ENABLE_PINNED_EVENTS" // This key will be used to identify clients with the old thread support enabled io.element.thread const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES" @@ -1114,8 +1114,8 @@ class VectorPreferences @Inject constructor( return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_ELEMENT_CALL_PERMISSION_SHORTCUTS, false) } - fun arePinnedMessagesEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_PINNED_MESSAGES, getDefault(R.bool.settings_labs_pinned_messages_default)) + fun arePinnedEventsEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_PINNED_EVENTS, getDefault(R.bool.settings_labs_pinned_events_default)) } /** diff --git a/vector/src/main/res/drawable/ic_open_pinned_messages.xml b/vector/src/main/res/drawable/ic_open_pinned_events.xml similarity index 100% rename from vector/src/main/res/drawable/ic_open_pinned_messages.xml rename to vector/src/main/res/drawable/ic_open_pinned_events.xml diff --git a/vector/src/main/res/drawable/ic_pin_event.xml b/vector/src/main/res/drawable/ic_pin_event.xml new file mode 100644 index 0000000000..b0341a8aa8 --- /dev/null +++ b/vector/src/main/res/drawable/ic_pin_event.xml @@ -0,0 +1,4 @@ + + + diff --git a/vector/src/main/res/drawable/ic_pin_message.xml b/vector/src/main/res/drawable/ic_pin_message.xml deleted file mode 100644 index 9fc7b8cecc..0000000000 --- a/vector/src/main/res/drawable/ic_pin_message.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/vector/src/main/res/drawable/ic_unpin_event.xml b/vector/src/main/res/drawable/ic_unpin_event.xml new file mode 100644 index 0000000000..514d21ec17 --- /dev/null +++ b/vector/src/main/res/drawable/ic_unpin_event.xml @@ -0,0 +1,4 @@ + + + diff --git a/vector/src/main/res/drawable/ic_unpin_message.xml b/vector/src/main/res/drawable/ic_unpin_message.xml deleted file mode 100644 index 0cad148ca7..0000000000 --- a/vector/src/main/res/drawable/ic_unpin_message.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/vector/src/main/res/layout/activity_pinned_messages.xml b/vector/src/main/res/layout/activity_pinned_events.xml similarity index 88% rename from vector/src/main/res/layout/activity_pinned_messages.xml rename to vector/src/main/res/layout/activity_pinned_events.xml index e7b0ef00c9..93a75fe2e3 100644 --- a/vector/src/main/res/layout/activity_pinned_messages.xml +++ b/vector/src/main/res/layout/activity_pinned_events.xml @@ -11,7 +11,7 @@ android:layout_height="match_parent"> --> + android:defaultValue="@bool/settings_labs_pinned_events_default" + android:key="SETTINGS_LABS_ENABLE_PINNED_EVENTS" + android:title="@string/labs_enable_pinned_events" />