mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Merge pull request #7378 from hayaksi1/fix/6853-remember-revealed-images
Keep manually revealed media visible when re-entering a room
This commit is contained in:
+3
-6
@@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateSetOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
@@ -20,7 +19,6 @@ import io.element.android.features.contentscanner.api.ContentScannerService
|
|||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.mapState
|
import io.element.android.libraries.core.coroutine.mapState
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
|
||||||
import io.element.android.libraries.matrix.api.media.MediaPreviewService
|
import io.element.android.libraries.matrix.api.media.MediaPreviewService
|
||||||
import io.element.android.libraries.matrix.api.media.isPreviewEnabled
|
import io.element.android.libraries.matrix.api.media.isPreviewEnabled
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||||
@@ -32,9 +30,8 @@ class TimelineProtectionPresenter(
|
|||||||
private val mediaPreviewService: MediaPreviewService,
|
private val mediaPreviewService: MediaPreviewService,
|
||||||
private val room: BaseRoom,
|
private val room: BaseRoom,
|
||||||
private val contentScannerService: ContentScannerService,
|
private val contentScannerService: ContentScannerService,
|
||||||
|
private val timelineProtectionStore: TimelineProtectionStore,
|
||||||
) : Presenter<TimelineProtectionState> {
|
) : Presenter<TimelineProtectionState> {
|
||||||
private val allowedEvents = mutableStateSetOf<EventId>()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): TimelineProtectionState {
|
override fun present(): TimelineProtectionState {
|
||||||
val mediaPreviewValue = remember {
|
val mediaPreviewValue = remember {
|
||||||
@@ -48,7 +45,7 @@ class TimelineProtectionPresenter(
|
|||||||
if (isPreviewEnabled) {
|
if (isPreviewEnabled) {
|
||||||
ProtectionState.RenderAll
|
ProtectionState.RenderAll
|
||||||
} else {
|
} else {
|
||||||
ProtectionState.RenderOnly(eventIds = allowedEvents.toImmutableSet())
|
ProtectionState.RenderOnly(eventIds = timelineProtectionStore.allowedEventIds.toImmutableSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +53,7 @@ class TimelineProtectionPresenter(
|
|||||||
fun handleEvent(event: TimelineProtectionEvent) {
|
fun handleEvent(event: TimelineProtectionEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is TimelineProtectionEvent.ShowContent -> {
|
is TimelineProtectionEvent.ShowContent -> {
|
||||||
allowedEvents += setOfNotNull(event.eventId)
|
event.eventId?.let(timelineProtectionStore::allowEvent)
|
||||||
}
|
}
|
||||||
is TimelineProtectionEvent.ValidateContent -> {
|
is TimelineProtectionEvent.ValidateContent -> {
|
||||||
contentScannerService.scan(event.mediaSources, event.validationState)
|
contentScannerService.scan(event.mediaSources, event.validationState)
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.protection
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateSetOf
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateSet
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
||||||
|
interface TimelineProtectionStore {
|
||||||
|
val allowedEventIds: SnapshotStateSet<EventId>
|
||||||
|
fun allowEvent(eventId: EventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
@SingleIn(SessionScope::class)
|
||||||
|
class DefaultTimelineProtectionStore : TimelineProtectionStore {
|
||||||
|
private val allowedEvents = mutableStateSetOf<EventId>()
|
||||||
|
|
||||||
|
override val allowedEventIds: SnapshotStateSet<EventId> = allowedEvents
|
||||||
|
|
||||||
|
override fun allowEvent(eventId: EventId) {
|
||||||
|
allowedEvents += eventId
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -95,6 +95,24 @@ class TimelineProtectionPresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - shown content is restored when the presenter is recreated`() = runTest {
|
||||||
|
val mediaPreviewConfig = MediaPreviewConfig(mediaPreviewValue = MediaPreviewValue.Off, hideInviteAvatar = false)
|
||||||
|
val mediaPreviewService = FakeMediaPreviewService(mediaPreviewConfigFlow = MutableStateFlow(mediaPreviewConfig))
|
||||||
|
val timelineProtectionStore = DefaultTimelineProtectionStore()
|
||||||
|
createPresenter(mediaPreviewService = mediaPreviewService, timelineProtectionStore = timelineProtectionStore).test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf()))
|
||||||
|
initialState.eventSink(TimelineProtectionEvent.ShowContent(eventId = AN_EVENT_ID))
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf(AN_EVENT_ID)))
|
||||||
|
}
|
||||||
|
createPresenter(mediaPreviewService = mediaPreviewService, timelineProtectionStore = timelineProtectionStore).test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf(AN_EVENT_ID)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `present - validate media scans the media source`() = runTest {
|
fun `present - validate media scans the media source`() = runTest {
|
||||||
@@ -134,9 +152,11 @@ class TimelineProtectionPresenterTest {
|
|||||||
room: BaseRoom = FakeBaseRoom(),
|
room: BaseRoom = FakeBaseRoom(),
|
||||||
mediaPreviewService: MediaPreviewService = FakeMediaPreviewService(),
|
mediaPreviewService: MediaPreviewService = FakeMediaPreviewService(),
|
||||||
contentScannerService: ContentScannerService = ContentScannerService { _: List<MediaSource>, _: ContentValidationState -> },
|
contentScannerService: ContentScannerService = ContentScannerService { _: List<MediaSource>, _: ContentValidationState -> },
|
||||||
|
timelineProtectionStore: TimelineProtectionStore = DefaultTimelineProtectionStore(),
|
||||||
) = TimelineProtectionPresenter(
|
) = TimelineProtectionPresenter(
|
||||||
mediaPreviewService = mediaPreviewService,
|
mediaPreviewService = mediaPreviewService,
|
||||||
room = room,
|
room = room,
|
||||||
contentScannerService = contentScannerService,
|
contentScannerService = contentScannerService,
|
||||||
|
timelineProtectionStore = timelineProtectionStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user