diff --git a/CHANGES.md b/CHANGES.md index 991078e704..574d3fe4be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Better connectivity lost indicator when airplane mode is on + - Add a setting to hide redacted events (#951) Bugfix 🐛: - Fix issues with FontScale switch (#69, #645) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt index 992cad41ca..154074b722 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt @@ -28,6 +28,10 @@ data class TimelineSettings( * A flag to filter edit events */ val filterEdits: Boolean = false, + /** + * A flag to filter redacted events + */ + val filterRedacted: Boolean = false, /** * A flag to filter by types. It should be used with [allowedTypes] field */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 5168d0728e..f798dbcf41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -62,8 +62,8 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) if (filterContentRelation) { liveEvents - ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE) - ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE) + ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) + ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) } val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) { sendingTimelineEvents diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt similarity index 54% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterContent.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt index 6e89a28b7d..ea8122bc6d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt @@ -16,8 +16,22 @@ package im.vector.matrix.android.internal.database.query -internal object FilterContent { +/** + * Query strings used to filter the timeline events regarding the Json raw string of the Event + */ +internal object TimelineEventFilter { + /** + * To apply to Event.content + */ + internal object Content { + internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}""" + internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"m.response"*}""" + } - internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}""" - internal const val RESPONSE_TYPE = """{*"m.relates_to"*"rel_type":*"m.response"*}""" + /** + * To apply to Event.unsigned + */ + internal object Unsigned { + internal const val REDACTED = """{*"redacted_because":*}""" + } } 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 f2bee734ce..bf6b81b57c 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 @@ -35,7 +35,7 @@ import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryE import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields -import im.vector.matrix.android.internal.database.query.FilterContent +import im.vector.matrix.android.internal.database.query.TimelineEventFilter import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.whereInRoom @@ -724,8 +724,11 @@ internal class DefaultTimeline( `in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray()) } if (settings.filterEdits) { - not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE) - not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE) + not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) + not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) + } + if (settings.filterRedacted) { + not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED) } return this } @@ -737,13 +740,19 @@ internal class DefaultTimeline( } else { true } + if (!filterType) return@filter false + val filterEdits = if (settings.filterEdits && it.root.type == EventType.MESSAGE) { val messageContent = it.root.content.toModel() messageContent?.relatesTo?.type != RelationType.REPLACE } else { true } - filterType && filterEdits + if (!filterEdits) return@filter false + + val filterRedacted = settings.filterRedacted && it.root.isRedacted() + + filterRedacted } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt index 056f942211..72e99701cd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntit import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields -import im.vector.matrix.android.internal.database.query.FilterContent +import im.vector.matrix.android.internal.database.query.TimelineEventFilter import im.vector.matrix.android.internal.database.query.whereInRoom import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm @@ -149,16 +149,21 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu */ private fun RealmQuery.filterReceiptsWithSettings(): RealmQuery { beginGroup() + var needOr = false if (settings.filterTypes) { not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray()) - } - if (settings.filterTypes && settings.filterEdits) { - or() + needOr = true } if (settings.filterEdits) { - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE) + if (needOr) or() + like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.EDIT) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.RESPONSE_TYPE) + like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.RESPONSE) + needOr = true + } + if (settings.filterRedacted) { + if (needOr) or() + like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED) } endGroup() return this diff --git a/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt b/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt index ac379a8f98..fa4b09ed4c 100644 --- a/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt +++ b/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt @@ -29,6 +29,10 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences: return vectorPreferences.showReadReceipts() } + fun shouldShowRedactedMessages(): Boolean { + return vectorPreferences.showRedactedMessages() + } + fun shouldShowLongClickOnRoomHelp(): Boolean { return vectorPreferences.shouldShowLongClickOnRoomHelp() } 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 d0dcac6ecc..4a767a178e 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 @@ -99,11 +99,13 @@ class RoomDetailViewModel @AssistedInject constructor( private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) { TimelineSettings(30, filterEdits = false, + filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(), filterTypes = false, buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) } else { TimelineSettings(30, filterEdits = true, + filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(), filterTypes = true, allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES, buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index c995c4d986..1455e2f8d8 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -88,6 +88,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY" private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY" private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY" + private const val SETTINGS_SHOW_REDACTED_KEY = "SETTINGS_SHOW_REDACTED_KEY" private const val SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY = "SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY" private const val SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY = "SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY" private const val SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY" @@ -625,6 +626,15 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_SHOW_READ_RECEIPTS_KEY, true) } + /** + * Tells if the redacted message should be shown + * + * @return true if the redacted should be shown + */ + fun showRedactedMessages(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_SHOW_REDACTED_KEY, true) + } + /** * Tells if the help on room list should be shown * diff --git a/vector/src/main/res/drawable/ic_trash_16.xml b/vector/src/main/res/drawable/ic_trash_16.xml new file mode 100644 index 0000000000..ca6052b447 --- /dev/null +++ b/vector/src/main/res/drawable/ic_trash_16.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/vector/src/main/res/drawable/ic_trash_24.xml b/vector/src/main/res/drawable/ic_trash_24.xml new file mode 100644 index 0000000000..266855d50c --- /dev/null +++ b/vector/src/main/res/drawable/ic_trash_24.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/vector/src/main/res/drawable/redacted_background.xml b/vector/src/main/res/drawable/redacted_background.xml deleted file mode 100644 index f253a9eaf7..0000000000 --- a/vector/src/main/res/drawable/redacted_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 3ae80424cc..7cc929306e 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -66,16 +66,15 @@ android:id="@+id/decorationSpace" android:layout_width="4dp" android:layout_height="8dp" - android:layout_toEndOf="@id/messageStartGuideline" - /> + android:layout_toEndOf="@id/messageStartGuideline" /> @@ -119,7 +118,7 @@ diff --git a/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml b/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml index 2f930577f0..acc60e6590 100644 --- a/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml @@ -1,4 +1,11 @@ - \ No newline at end of file + android:layout_height="wrap_content" + android:drawableStart="@drawable/ic_trash_16" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:text="@string/event_redacted" + android:textColor="?riotx_text_primary_body_contrast" + android:textSize="14sp" + app:drawableTint="?riotx_text_primary_body_contrast" /> \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 4022b1ef92..4dab1d3da0 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1563,6 +1563,9 @@ Why choose Riot.im? View Reactions Reactions + Message deleted + Show removed messages + Show a placeholder for removed messages Event deleted by user Event moderated by room admin Last edited by %1$s on %2$s diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index b1bd6ea5e6..d290b62825 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -70,6 +70,12 @@ android:summary="@string/settings_show_read_receipts_summary" android:title="@string/settings_show_read_receipts" /> + +