Skip to content

Commit 779c6db

Browse files
authored
Merge pull request #3592 from element-hq/feature/bma/hideImages
Add developer setting to hide images in the timeline
2 parents 98d9abe + 1f7b05a commit 779c6db

File tree

86 files changed

+1103
-193
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1103
-193
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
4646
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
4747
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
4848
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
49+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
4950
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
5051
import io.element.android.features.networkmonitor.api.NetworkMonitor
5152
import io.element.android.features.networkmonitor.api.NetworkStatus
@@ -90,6 +91,7 @@ class MessagesPresenter @AssistedInject constructor(
9091
private val composerPresenter: MessageComposerPresenter,
9192
private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter,
9293
timelinePresenterFactory: TimelinePresenter.Factory,
94+
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
9395
private val actionListPresenterFactory: ActionListPresenter.Factory,
9496
private val customReactionPresenter: CustomReactionPresenter,
9597
private val reactionSummaryPresenter: ReactionSummaryPresenter,
@@ -123,6 +125,7 @@ class MessagesPresenter @AssistedInject constructor(
123125
val composerState = composerPresenter.present()
124126
val voiceMessageComposerState = voiceMessageComposerPresenter.present()
125127
val timelineState = timelinePresenter.present()
128+
val timelineProtectionState = timelineProtectionPresenter.present()
126129
val actionListState = actionListPresenter.present()
127130
val customReactionState = customReactionPresenter.present()
128131
val reactionSummaryState = reactionSummaryPresenter.present()
@@ -182,6 +185,7 @@ class MessagesPresenter @AssistedInject constructor(
182185
composerState = composerState,
183186
enableTextFormatting = composerState.showTextFormatting,
184187
timelineState = timelineState,
188+
timelineProtectionState = timelineProtectionState,
185189
)
186190
}
187191
is MessagesEvents.ToggleReaction -> {
@@ -213,6 +217,7 @@ class MessagesPresenter @AssistedInject constructor(
213217
userEventPermissions = userEventPermissions,
214218
voiceMessageComposerState = voiceMessageComposerState,
215219
timelineState = timelineState,
220+
timelineProtectionState = timelineProtectionState,
216221
actionListState = actionListState,
217222
customReactionState = customReactionState,
218223
reactionSummaryState = reactionSummaryState,
@@ -262,6 +267,7 @@ class MessagesPresenter @AssistedInject constructor(
262267
action: TimelineItemAction,
263268
targetEvent: TimelineItem.Event,
264269
composerState: MessageComposerState,
270+
timelineProtectionState: TimelineProtectionState,
265271
enableTextFormatting: Boolean,
266272
timelineState: TimelineState,
267273
) = launch {
@@ -271,7 +277,7 @@ class MessagesPresenter @AssistedInject constructor(
271277
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
272278
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
273279
TimelineItemAction.Reply,
274-
TimelineItemAction.ReplyInThread -> handleActionReply(targetEvent, composerState)
280+
TimelineItemAction.ReplyInThread -> handleActionReply(targetEvent, composerState, timelineProtectionState)
275281
TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent)
276282
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
277283
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
@@ -385,11 +391,18 @@ class MessagesPresenter @AssistedInject constructor(
385391
}
386392
}
387393

388-
private suspend fun handleActionReply(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
394+
private suspend fun handleActionReply(
395+
targetEvent: TimelineItem.Event,
396+
composerState: MessageComposerState,
397+
timelineProtectionState: TimelineProtectionState,
398+
) {
389399
if (targetEvent.eventId == null) return
390400
timelineController.invokeOnCurrentTimeline {
391401
val replyToDetails = loadReplyDetails(targetEvent.eventId).map(permalinkParser)
392-
val composerMode = MessageComposerMode.Reply(replyToDetails = replyToDetails)
402+
val composerMode = MessageComposerMode.Reply(
403+
replyToDetails = replyToDetails,
404+
hideImage = timelineProtectionState.hideMediaContent(targetEvent.eventId),
405+
)
393406
composerState.eventSink(
394407
MessageComposerEvents.SetMode(composerMode)
395408
)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.element.android.features.messages.impl.timeline.TimelineState
1515
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
1616
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
1717
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
18+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
1819
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
1920
import io.element.android.libraries.architecture.AsyncData
2021
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@@ -32,6 +33,7 @@ data class MessagesState(
3233
val composerState: MessageComposerState,
3334
val voiceMessageComposerState: VoiceMessageComposerState,
3435
val timelineState: TimelineState,
36+
val timelineProtectionState: TimelineProtectionState,
3537
val actionListState: ActionListState,
3638
val customReactionState: CustomReactionState,
3739
val reactionSummaryState: ReactionSummaryState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot
2626
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
2727
import io.element.android.features.messages.impl.timeline.model.TimelineItem
2828
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
29+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
30+
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
2931
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
3032
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
3133
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState
@@ -103,6 +105,7 @@ fun aMessagesState(
103105
// Render a focused event for an event with sender information displayed
104106
focusedEventIndex = 2,
105107
),
108+
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
106109
readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(),
107110
actionListState: ActionListState = anActionListState(),
108111
customReactionState: CustomReactionState = aCustomReactionState(),
@@ -121,6 +124,7 @@ fun aMessagesState(
121124
userEventPermissions = userEventPermissions,
122125
composerState = composerState,
123126
voiceMessageComposerState = voiceMessageComposerState,
127+
timelineProtectionState = timelineProtectionState,
124128
timelineState = timelineState,
125129
readReceiptBottomSheetState = readReceiptBottomSheetState,
126130
actionListState = actionListState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ private fun MessagesViewContent(
379379
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberExitOnScrollBehavior()
380380
TimelineView(
381381
state = state.timelineState,
382+
timelineProtectionState = state.timelineProtectionState,
382383
onUserDataClick = onUserDataClick,
383384
onLinkClick = onLinkClick,
384385
onMessageClick = onMessageClick,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import io.element.android.features.messages.impl.crypto.sendfailure.resolve.Reso
1414
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
1515
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
1616
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
17+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionPresenter
18+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
1719
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
1820
import io.element.android.features.messages.impl.typing.TypingNotificationState
1921
import io.element.android.libraries.architecture.Presenter
@@ -30,4 +32,7 @@ interface MessagesModule {
3032

3133
@Binds
3234
fun bindTypingNotificationPresenter(presenter: TypingNotificationPresenter): Presenter<TypingNotificationState>
35+
36+
@Binds
37+
fun bindTimelineProtectionPresenter(presenter: TimelineProtectionPresenter): Presenter<TimelineProtectionState>
3338
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,18 @@ class MessageComposerPresenter @Inject constructor(
585585
content = htmlText ?: markdownText
586586
)
587587
is ComposerDraftType.Reply -> {
588-
messageComposerContext.composerMode = MessageComposerMode.Reply(InReplyToDetails.Loading(draftType.eventId))
588+
messageComposerContext.composerMode = MessageComposerMode.Reply(
589+
replyToDetails = InReplyToDetails.Loading(draftType.eventId),
590+
// I guess it's fine to always render the image when restoring a draft
591+
hideImage = false
592+
)
589593
timelineController.invokeOnCurrentTimeline {
590594
val replyToDetails = loadReplyDetails(draftType.eventId).map(permalinkParser)
591-
run { messageComposerContext.composerMode = MessageComposerMode.Reply(replyToDetails) }
595+
messageComposerContext.composerMode = MessageComposerMode.Reply(
596+
replyToDetails = replyToDetails,
597+
// I guess it's fine to always render the image when restoring a draft
598+
hideImage = false
599+
)
592600
}
593601
}
594602
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
3030
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
3131
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
3232
import io.element.android.features.messages.impl.timeline.model.TimelineItem
33+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
3334
import io.element.android.features.messages.impl.typing.TypingNotificationState
3435
import io.element.android.libraries.architecture.AsyncData
3536
import io.element.android.libraries.architecture.Presenter
@@ -60,6 +61,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
6061
private val room: MatrixRoom,
6162
timelineItemsFactoryCreator: TimelineItemsFactory.Creator,
6263
private val timelineProvider: PinnedEventsTimelineProvider,
64+
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
6365
private val snackbarDispatcher: SnackbarDispatcher,
6466
actionListPresenterFactory: ActionListPresenter.Factory,
6567
private val appCoroutineScope: CoroutineScope,
@@ -97,14 +99,13 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
9799
)
98100
)
99101
}
100-
102+
val timelineProtectionState = timelineProtectionPresenter.present()
101103
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
102104
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
103105

104106
var pinnedMessageItems by remember {
105107
mutableStateOf<AsyncData<ImmutableList<TimelineItem>>>(AsyncData.Uninitialized)
106108
}
107-
108109
PinnedMessagesListEffect(
109110
onItemsChange = { newItems ->
110111
pinnedMessageItems = newItems
@@ -119,6 +120,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
119120

120121
return pinnedMessagesListState(
121122
timelineRoomInfo = timelineRoomInfo,
123+
timelineProtectionState = timelineProtectionState,
122124
userEventPermissions = userEventPermissions,
123125
timelineItems = pinnedMessageItems,
124126
eventSink = ::handleEvents
@@ -214,6 +216,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
214216
@Composable
215217
private fun pinnedMessagesListState(
216218
timelineRoomInfo: TimelineRoomInfo,
219+
timelineProtectionState: TimelineProtectionState,
217220
userEventPermissions: UserEventPermissions,
218221
timelineItems: AsyncData<ImmutableList<TimelineItem>>,
219222
eventSink: (PinnedMessagesListEvents) -> Unit
@@ -228,6 +231,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
228231
val actionListState = actionListPresenter.present()
229232
PinnedMessagesListState.Filled(
230233
timelineRoomInfo = timelineRoomInfo,
234+
timelineProtectionState = timelineProtectionState,
231235
userEventPermissions = userEventPermissions,
232236
timelineItems = timelineItems.data,
233237
actionListState = actionListState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.element.android.features.messages.impl.UserEventPermissions
1515
import io.element.android.features.messages.impl.actionlist.ActionListState
1616
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
1717
import io.element.android.features.messages.impl.timeline.model.TimelineItem
18+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
1819
import io.element.android.libraries.ui.strings.CommonPlurals
1920
import io.element.android.libraries.ui.strings.CommonStrings
2021
import kotlinx.collections.immutable.ImmutableList
@@ -26,6 +27,7 @@ sealed interface PinnedMessagesListState {
2627
data object Empty : PinnedMessagesListState
2728
data class Filled(
2829
val timelineRoomInfo: TimelineRoomInfo,
30+
val timelineProtectionState: TimelineProtectionState,
2931
val userEventPermissions: UserEventPermissions,
3032
val timelineItems: ImmutableList<TimelineItem>,
3133
val actionListState: ActionListState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
2222
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent
2323
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
2424
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
25+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
26+
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
2527
import kotlinx.collections.immutable.persistentListOf
2628
import kotlinx.collections.immutable.toImmutableList
2729

@@ -83,12 +85,14 @@ fun anEmptyPinnedMessagesListState() = PinnedMessagesListState.Empty
8385

8486
fun aLoadedPinnedMessagesListState(
8587
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
88+
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
8689
timelineItems: List<TimelineItem> = emptyList(),
8790
actionListState: ActionListState = anActionListState(),
8891
aUserEventPermissions: UserEventPermissions = UserEventPermissions.DEFAULT,
8992
eventSink: (PinnedMessagesListEvents) -> Unit = {}
9093
) = PinnedMessagesListState.Filled(
9194
timelineRoomInfo = timelineRoomInfo,
95+
timelineProtectionState = timelineProtectionState,
9296
timelineItems = timelineItems.toImmutableList(),
9397
actionListState = actionListState,
9498
userEventPermissions = aUserEventPermissions,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import io.element.android.features.messages.impl.timeline.components.event.Timel
3232
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
3333
import io.element.android.features.messages.impl.timeline.model.TimelineItem
3434
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
35+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
36+
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
3537
import io.element.android.features.poll.api.pollcontent.PollTitleView
3638
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
3739
import io.element.android.libraries.designsystem.components.button.BackButton
@@ -77,8 +79,8 @@ fun PinnedMessagesListView(
7779
onLinkClick = onLinkClick,
7880
onErrorDismiss = onBackClick,
7981
modifier = Modifier
80-
.padding(padding)
81-
.consumeWindowInsets(padding),
82+
.padding(padding)
83+
.consumeWindowInsets(padding),
8284
)
8385
}
8486
)
@@ -208,6 +210,7 @@ private fun PinnedMessagesListLoaded(
208210
timelineItem = timelineItem,
209211
timelineRoomInfo = state.timelineRoomInfo,
210212
renderReadReceipts = false,
213+
timelineProtectionState = state.timelineProtectionState,
211214
isLastOutgoingMessage = false,
212215
focusedEventId = null,
213216
onUserDataClick = onUserDataClick,
@@ -225,6 +228,7 @@ private fun PinnedMessagesListLoaded(
225228
eventContentView = { event, contentModifier, onContentLayoutChange ->
226229
TimelineItemEventContentViewWrapper(
227230
event = event,
231+
timelineProtectionState = state.timelineProtectionState,
228232
onLinkClick = onLinkClick,
229233
modifier = contentModifier,
230234
onContentLayoutChange = onContentLayoutChange
@@ -238,6 +242,7 @@ private fun PinnedMessagesListLoaded(
238242
@Composable
239243
private fun TimelineItemEventContentViewWrapper(
240244
event: TimelineItem.Event,
245+
timelineProtectionState: TimelineProtectionState,
241246
onLinkClick: (String) -> Unit,
242247
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
243248
modifier: Modifier = Modifier,
@@ -251,6 +256,8 @@ private fun TimelineItemEventContentViewWrapper(
251256
} else {
252257
TimelineItemEventContentView(
253258
content = event.content,
259+
hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId),
260+
onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) },
254261
onLinkClick = onLinkClick,
255262
eventSink = { },
256263
modifier = modifier,

0 commit comments

Comments
 (0)