Skip to content

Added lab feature to pin/unpin messages #7762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7762.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added lab feature to pin/unpin messages
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
<string name="notice_room_server_acl_set_allowed">• Servers matching %s are allowed.</string>
<string name="notice_room_server_acl_set_ip_literals_allowed">• Servers matching IP literals are allowed.</string>
<string name="notice_room_server_acl_set_ip_literals_not_allowed">• Servers matching IP literals are banned.</string>
<string name="notice_user_pinned_event">%1$s pinned a message.</string>
<string name="notice_user_unpinned_event">%1$s unpinned a message.</string>
<string name="notice_user_pinned_event_by_you">You pinned a message.</string>
<string name="notice_user_unpinned_event_by_you">You unpinned a message.</string>

<string name="notice_room_server_acl_updated_title">%s changed the server ACLs for this room.</string>
<string name="notice_room_server_acl_updated_title_by_you">You changed the server ACLs for this room.</string>
Expand Down Expand Up @@ -373,6 +377,7 @@
<string name="action_sign_out_confirmation_simple">Are you sure you want to sign out?</string>
<string name="action_voice_call">Voice Call</string>
<string name="action_video_call">Video Call</string>
<string name="action_open_pinned_events">Open Pinned Messages</string>
<string name="action_view_threads">View Threads</string>
<string name="action_mark_all_as_read">Mark all as read</string>
<string name="action_quick_reply">Quick reply</string>
Expand Down Expand Up @@ -801,6 +806,10 @@
<string name="threads_labs_enable_notice_title">Threads Beta</string>
<string name="threads_labs_enable_notice_message">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?</string>

<!-- Pinning -->
<string name="pinning_event">Pin</string>
<string name="unpinning_event">Unpin</string>
<string name="pinned_events_timeline_title">Pinned Messages</string>

<!-- Search -->
<string name="search_hint">Search</string>
Expand Down Expand Up @@ -3032,6 +3041,7 @@

<string name="labs_auto_report_uisi">Auto Report Decryption Errors.</string>
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
<string name="labs_enable_pinned_events">Enable Pinned Messages</string>
<string name="labs_enable_thread_messages">Enable Thread Messages</string>
<string name="labs_enable_thread_messages_desc">Note: app will be restarted</string>
<string name="settings_show_latest_profile">Show latest user info</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.model.pinnedmessages.PinnedEventsStateContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.model.relation.isReply
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
Expand Down Expand Up @@ -447,3 +448,11 @@ fun Event.supportsNotification() =

fun Event.isContentReportable() =
this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values

fun Event.getIdsOfPinnedEvents(): List<String>? {
return getClearContent()?.toModel<PinnedEventsStateContent>()?.eventIds
}

fun Event.getPreviousIdsOfPinnedEvents(): List<String>? {
return resolvedPrevContent()?.toModel<PinnedEventsStateContent>()?.eventIds
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ object EventType {
const val STATE_ROOM_NAME = "m.room.name"
const val STATE_ROOM_TOPIC = "m.room.topic"
const val STATE_ROOM_AVATAR = "m.room.avatar"
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
const val STATE_ROOM_MEMBER = "m.room.member"
const val STATE_ROOM_THIRD_PARTY_INVITE = "m.room.third_party_invite"
const val STATE_ROOM_CREATE = "m.room.create"
Expand All @@ -67,7 +68,6 @@ object EventType {
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"
const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility"
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
const val STATE_ROOM_SERVER_ACL = "m.room.server_acl"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.session.room.model.pinnedmessages

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* Class representing a pinned event content.
*/
@JsonClass(generateAdapter = true)
data class PinnedEventsStateContent(
@Json(name = "pinned") val eventIds: List<String>
)
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ interface StateService {
*/
suspend fun deleteAvatar()

/**
* Pin an event of the room.
*/
suspend fun pinEvent(eventId: String)

/**
* Unpin an event of the room.
*/
suspend fun unpinEvent(eventId: String)

/**
* Send a state event to the room.
* @param eventType The type of event to send.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ data class TimelineSettings(
* The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline.
*/
val rootThreadEventId: String? = null,
/**
* True if the timeline is a pinned messages timeline.
*/
val isFromPinnedEventsTimeline: Boolean = false,
/**
* If true Sender Info shown in room will get the latest data information (avatar + displayName).
*/
Expand All @@ -42,4 +46,9 @@ data class TimelineSettings(
* Returns true if this is a thread timeline or false otherwise.
*/
fun isThreadTimeline() = rootThreadEventId != null

/**
* Returns true if this is a pinned messages timeline or false otherwise.
*/
fun isPinnedEventsTimeline() = isFromPinnedEventsTimeline
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,22 @@ internal interface RoomAPI {
): SendResponse

/**
* Get state events of a room
* Get all state events of a room
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
suspend fun getRoomState(@Path("roomId") roomId: String): List<Event>
suspend fun getAllRoomStates(@Path("roomId") roomId: String): List<Event>

/**
* Get specific state event of a room
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{eventType}/{state_key}")
suspend fun getRoomState(
@Path("roomId") roomId: String,
@Path("eventType") eventType: String,
@Path("state_key") stateKey: String
): Content

/**
* Paginate relations for event based in normal topological order.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal class DefaultResolveRoomStateTask @Inject constructor(

override suspend fun execute(params: ResolveRoomStateTask.Params): List<Event> {
return executeRequest(globalErrorReceiver) {
roomAPI.getRoomState(params.roomId)
roomAPI.getAllRoomStates(params.roomId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.query.QueryStateEventValue
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event
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.toContent
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.pinnedmessages.PinnedEventsStateContent
import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes
Expand Down Expand Up @@ -170,6 +173,32 @@ internal class DefaultStateService @AssistedInject constructor(
)
}

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<String>) {
sendStateEvent(
eventType = EventType.STATE_ROOM_PINNED_EVENT,
body = PinnedEventsStateContent(eventIds).toContent(),
stateKey = ""
)
}

override suspend fun setJoinRulePublic() {
updateJoinRule(RoomJoinRules.PUBLIC, null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
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.room.model.Membership
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
Expand Down Expand Up @@ -63,7 +66,8 @@ internal class DefaultTimeline(
private val settings: TimelineSettings,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val clock: Clock,
stateEventDataSource: StateEventDataSource,
private val stateEventDataSource: StateEventDataSource,
private val timelineEventDataSource: TimelineEventDataSource,
paginationTask: PaginationTask,
getEventTask: GetContextOfEventTask,
fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
Expand Down Expand Up @@ -95,6 +99,8 @@ internal class DefaultTimeline(
private var isFromThreadTimeline = false
private var rootThreadEventId: String? = null

private var isFromPinnedEventsTimeline = false

private val strategyDependencies = LoadTimelineStrategy.Dependencies(
timelineSettings = settings,
realm = backgroundRealm,
Expand Down Expand Up @@ -125,7 +131,11 @@ internal class DefaultTimeline(
override fun addListener(listener: Timeline.Listener): Boolean {
listeners.add(listener)
timelineScope.launch {
val snapshot = strategy.buildSnapshot()
val snapshot = if (isFromPinnedEventsTimeline) {
getPinnedEvents()
} else {
strategy.buildSnapshot()
}
withContext(coroutineDispatchers.main) {
tryOrNull { listener.onTimelineUpdated(snapshot) }
}
Expand All @@ -141,7 +151,7 @@ internal class DefaultTimeline(
listeners.clear()
}

override fun start(rootThreadEventId: String?) {
override fun start(rootThreadEventId: String?, isFromPinnedEventsTimeline: Boolean) {
timelineScope.launch {
loadRoomMembersIfNeeded()
}
Expand All @@ -150,6 +160,7 @@ internal class DefaultTimeline(
if (isStarted.compareAndSet(false, true)) {
isFromThreadTimeline = rootThreadEventId != null
this@DefaultTimeline.rootThreadEventId = rootThreadEventId
this@DefaultTimeline.isFromPinnedEventsTimeline = isFromPinnedEventsTimeline
// /
val realm = Realm.getInstance(realmConfiguration)
ensureReadReceiptAreLoaded(realm)
Expand Down Expand Up @@ -254,7 +265,12 @@ internal class DefaultTimeline(
}
}
Timber.v("$baseLogMessage: result $loadMoreResult")
val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END
val hasMoreToLoad = if (isFromPinnedEventsTimeline) {
!areAllPinnedEventsLoaded()
} else {
loadMoreResult != LoadMoreResult.REACHED_END
}

updateState(direction) {
it.copy(loading = false, hasMoreToLoad = hasMoreToLoad)
}
Expand Down Expand Up @@ -334,7 +350,11 @@ internal class DefaultTimeline(
}

private suspend fun postSnapshot() {
val snapshot = strategy.buildSnapshot()
val snapshot = if (isFromPinnedEventsTimeline) {
getPinnedEvents()
} else {
strategy.buildSnapshot()
}
Timber.v("Post snapshot of ${snapshot.size} events")
withContext(coroutineDispatchers.main) {
listeners.forEach {
Expand All @@ -349,6 +369,25 @@ internal class DefaultTimeline(
}
}

private fun getIdsOfPinnedEvents(): List<String> {
return stateEventDataSource
.getStateEvent(roomId, EventType.STATE_ROOM_PINNED_EVENT, QueryStringValue.Equals(""))
?.getIdsOfPinnedEvents()
.orEmpty()
}

private fun getPinnedEvents(): List<TimelineEvent> {
return getIdsOfPinnedEvents()
.mapNotNull { id ->
timelineEventDataSource.getTimelineEvent(roomId, id)
}
.reversed()
}

private fun areAllPinnedEventsLoaded(): Boolean {
return getIdsOfPinnedEvents().size == getPinnedEvents().size
}

private fun onNewTimelineEvents(eventIds: List<String>) {
timelineScope.launch(coroutineDispatchers.main) {
listeners.forEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
lightweightSettingsStorage = lightweightSettingsStorage,
clock = clock,
stateEventDataSource = stateEventDataSource,
timelineEventDataSource = timelineEventDataSource,
)
}

Expand Down
1 change: 1 addition & 0 deletions vector-config/src/main/res/values/config-settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<!-- Level 1: Labs -->
<bool name="settings_labs_deferred_dm_visible">true</bool>
<bool name="settings_labs_deferred_dm_default">true</bool>
<bool name="settings_labs_pinned_events_default">false</bool>
<bool name="settings_labs_thread_messages_default">false</bool>
<bool name="settings_labs_new_app_layout_default">true</bool>
<bool name="settings_labs_new_session_manager_default">false</bool>
Expand Down
1 change: 1 addition & 0 deletions vector/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
<activity android:name=".features.roomdirectory.roompreview.RoomPreviewActivity" />
<activity android:name=".features.home.room.filtered.FilteredRoomsActivity" />
<activity android:name=".features.home.room.threads.ThreadsActivity" />
<activity android:name=".features.home.room.pinnedmessages.PinnedEventsActivity" />

<activity
android:name=".features.home.room.detail.RoomDetailActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.util.MatrixItem

sealed class RoomDetailAction : VectorViewModelAction {
data class PinEvent(val eventId: String) : RoomDetailAction()
data class UnpinEvent(val eventId: String) : RoomDetailAction()
data class SendSticker(val stickerContent: MessageStickerContent) : RoomDetailAction()
data class SendMedia(val attachments: List<ContentAttachmentData>, val compressBeforeSending: Boolean) : RoomDetailAction()
data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailAction()
Expand Down
Loading