Remove "history may be shared" banner. (#6087)
* Revert "Add alert to encrypted rooms with visible history (Android). (#5709)"
This reverts commit 59f51d8627.
* fix: Restore identity state change preview and snapshot.
This commit is contained in:
parent
61cbc54942
commit
03bd85b3fc
25 changed files with 4 additions and 502 deletions
|
|
@ -68,7 +68,6 @@ dependencies {
|
||||||
implementation(libs.jsoup)
|
implementation(libs.jsoup)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.androidx.constraintlayout.compose)
|
implementation(libs.androidx.constraintlayout.compose)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
|
||||||
implementation(libs.androidx.media3.exoplayer)
|
implementation(libs.androidx.media3.exoplayer)
|
||||||
implementation(libs.androidx.media3.ui)
|
implementation(libs.androidx.media3.ui)
|
||||||
implementation(libs.sigpwned.emoji4j)
|
implementation(libs.sigpwned.emoji4j)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import io.element.android.appconfig.MessageComposerConfig
|
||||||
import io.element.android.features.messages.api.timeline.HtmlConverterProvider
|
import io.element.android.features.messages.api.timeline.HtmlConverterProvider
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
|
|
||||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
||||||
import io.element.android.features.messages.impl.link.LinkState
|
import io.element.android.features.messages.impl.link.LinkState
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
|
||||||
|
|
@ -102,7 +101,6 @@ class MessagesPresenter(
|
||||||
@Assisted private val timelinePresenter: Presenter<TimelineState>,
|
@Assisted private val timelinePresenter: Presenter<TimelineState>,
|
||||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||||
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
|
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
|
||||||
private val historyVisibleStatePresenter: Presenter<HistoryVisibleState>,
|
|
||||||
private val linkPresenter: Presenter<LinkState>,
|
private val linkPresenter: Presenter<LinkState>,
|
||||||
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
||||||
private val customReactionPresenter: Presenter<CustomReactionState>,
|
private val customReactionPresenter: Presenter<CustomReactionState>,
|
||||||
|
|
@ -154,7 +152,6 @@ class MessagesPresenter(
|
||||||
val timelineState = timelinePresenter.present()
|
val timelineState = timelinePresenter.present()
|
||||||
val timelineProtectionState = timelineProtectionPresenter.present()
|
val timelineProtectionState = timelineProtectionPresenter.present()
|
||||||
val identityChangeState = identityChangeStatePresenter.present()
|
val identityChangeState = identityChangeStatePresenter.present()
|
||||||
val historyVisibleState = historyVisibleStatePresenter.present()
|
|
||||||
val actionListState = actionListPresenter.present()
|
val actionListState = actionListPresenter.present()
|
||||||
val linkState = linkPresenter.present()
|
val linkState = linkPresenter.present()
|
||||||
val customReactionState = customReactionPresenter.present()
|
val customReactionState = customReactionPresenter.present()
|
||||||
|
|
@ -286,7 +283,6 @@ class MessagesPresenter(
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
timelineProtectionState = timelineProtectionState,
|
timelineProtectionState = timelineProtectionState,
|
||||||
identityChangeState = identityChangeState,
|
identityChangeState = identityChangeState,
|
||||||
historyVisibleState = historyVisibleState,
|
|
||||||
linkState = linkState,
|
linkState = linkState,
|
||||||
actionListState = actionListState,
|
actionListState = actionListState,
|
||||||
customReactionState = customReactionState,
|
customReactionState = customReactionState,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.features.messages.impl
|
||||||
|
|
||||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
|
|
||||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
||||||
import io.element.android.features.messages.impl.link.LinkState
|
import io.element.android.features.messages.impl.link.LinkState
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||||
|
|
@ -41,7 +40,6 @@ data class MessagesState(
|
||||||
val timelineState: TimelineState,
|
val timelineState: TimelineState,
|
||||||
val timelineProtectionState: TimelineProtectionState,
|
val timelineProtectionState: TimelineProtectionState,
|
||||||
val identityChangeState: IdentityChangeState,
|
val identityChangeState: IdentityChangeState,
|
||||||
val historyVisibleState: HistoryVisibleState,
|
|
||||||
val linkState: LinkState,
|
val linkState: LinkState,
|
||||||
val actionListState: ActionListState,
|
val actionListState: ActionListState,
|
||||||
val customReactionState: CustomReactionState,
|
val customReactionState: CustomReactionState,
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
|
||||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState
|
import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
|
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState
|
|
||||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
||||||
import io.element.android.features.messages.impl.crypto.identity.aRoomMemberIdentityStateChange
|
import io.element.android.features.messages.impl.crypto.identity.aRoomMemberIdentityStateChange
|
||||||
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
||||||
|
|
@ -92,15 +90,6 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||||
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
|
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
|
||||||
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange()))
|
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange()))
|
||||||
),
|
),
|
||||||
aMessagesState(
|
|
||||||
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
|
|
||||||
historyVisibleState = aHistoryVisibleState(showAlert = true)
|
|
||||||
),
|
|
||||||
aMessagesState(
|
|
||||||
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
|
|
||||||
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())),
|
|
||||||
historyVisibleState = aHistoryVisibleState(showAlert = true)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +110,6 @@ fun aMessagesState(
|
||||||
),
|
),
|
||||||
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
|
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
|
||||||
identityChangeState: IdentityChangeState = anIdentityChangeState(),
|
identityChangeState: IdentityChangeState = anIdentityChangeState(),
|
||||||
historyVisibleState: HistoryVisibleState = aHistoryVisibleState(),
|
|
||||||
linkState: LinkState = aLinkState(),
|
linkState: LinkState = aLinkState(),
|
||||||
readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(),
|
readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(),
|
||||||
actionListState: ActionListState = anActionListState(),
|
actionListState: ActionListState = anActionListState(),
|
||||||
|
|
@ -145,7 +133,6 @@ fun aMessagesState(
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
timelineProtectionState = timelineProtectionState,
|
timelineProtectionState = timelineProtectionState,
|
||||||
identityChangeState = identityChangeState,
|
identityChangeState = identityChangeState,
|
||||||
historyVisibleState = historyVisibleState,
|
|
||||||
linkState = linkState,
|
linkState = linkState,
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
readReceiptBottomSheetState = readReceiptBottomSheetState,
|
readReceiptBottomSheetState = readReceiptBottomSheetState,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListView
|
import io.element.android.features.messages.impl.actionlist.ActionListView
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView
|
|
||||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView
|
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView
|
||||||
import io.element.android.features.messages.impl.link.LinkEvent
|
import io.element.android.features.messages.impl.link.LinkEvent
|
||||||
import io.element.android.features.messages.impl.link.LinkView
|
import io.element.android.features.messages.impl.link.LinkView
|
||||||
|
|
@ -520,17 +519,10 @@ private fun MessagesViewComposerBottomSheetContents(
|
||||||
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
|
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
|
||||||
if (state.composerState.suggestions.isEmpty() &&
|
if (state.composerState.suggestions.isEmpty() &&
|
||||||
state.composerState.textEditorState is TextEditorState.Markdown) {
|
state.composerState.textEditorState is TextEditorState.Markdown) {
|
||||||
if (state.identityChangeState.roomMemberIdentityStateChanges.isNotEmpty()) {
|
IdentityChangeStateView(
|
||||||
IdentityChangeStateView(
|
state = state.identityChangeState,
|
||||||
state = state.identityChangeState,
|
onLinkClick = onLinkClick,
|
||||||
onLinkClick = onLinkClick,
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
HistoryVisibleStateView(
|
|
||||||
state = state.historyVisibleState,
|
|
||||||
onLinkClick = onLinkClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
|
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
|
||||||
it.identityState == IdentityState.VerificationViolation
|
it.identityState == IdentityState.VerificationViolation
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.edit
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
|
||||||
import io.element.android.libraries.androidutils.hash.hash
|
|
||||||
import io.element.android.libraries.di.SessionScope
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
|
||||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
interface HistoryVisibleAcknowledgementRepository {
|
|
||||||
fun hasAcknowledged(roomId: RoomId): Flow<Boolean>
|
|
||||||
suspend fun setAcknowledged(roomId: RoomId, value: Boolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
|
||||||
class DefaultHistoryVisibleAcknowledgementRepository(
|
|
||||||
sessionId: SessionId,
|
|
||||||
preferenceDataStoreFactory: PreferenceDataStoreFactory,
|
|
||||||
) : HistoryVisibleAcknowledgementRepository {
|
|
||||||
val store =
|
|
||||||
sessionId.value.hash().take(16).let { hash ->
|
|
||||||
preferenceDataStoreFactory.create("elementx_historyvisible_$hash")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasAcknowledged(roomId: RoomId): Flow<Boolean> {
|
|
||||||
return store.data.map { prefs ->
|
|
||||||
val acknowledged = prefs[booleanPreferencesKey(roomId.value)] ?: false
|
|
||||||
acknowledged
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) {
|
|
||||||
store.edit { prefs ->
|
|
||||||
prefs[booleanPreferencesKey(roomId.value)] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
sealed interface HistoryVisibleEvent {
|
|
||||||
data object Acknowledge : HistoryVisibleEvent
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
data class HistoryVisibleState(
|
|
||||||
val showAlert: Boolean,
|
|
||||||
val eventSink: (HistoryVisibleEvent) -> Unit,
|
|
||||||
)
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import dev.zacsweers.metro.Inject
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
class HistoryVisibleStatePresenter(
|
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
private val repository: HistoryVisibleAcknowledgementRepository,
|
|
||||||
private val room: JoinedRoom,
|
|
||||||
) : Presenter<HistoryVisibleState> {
|
|
||||||
@Composable
|
|
||||||
override fun present(): HistoryVisibleState {
|
|
||||||
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false)
|
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
|
||||||
// Implicitly assume the alert is initially acknowledged to avoid flashes in UI.
|
|
||||||
val acknowledged by repository.hasAcknowledged(room.roomId).collectAsState(initial = true)
|
|
||||||
val isHistoryVisible = roomInfo.historyVisibility == RoomHistoryVisibility.Shared || roomInfo.historyVisibility == RoomHistoryVisibility.WorldReadable
|
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
LaunchedEffect(isHistoryVisible, acknowledged) {
|
|
||||||
if (!isHistoryVisible && acknowledged) {
|
|
||||||
// Clear the dismissed flag, if it is set to ensure that if a room is changed public -> private -> public,
|
|
||||||
// we show the banner again when it is set back to public.
|
|
||||||
repository.setAcknowledged(room.roomId, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleEvent(event: HistoryVisibleEvent) {
|
|
||||||
when (event) {
|
|
||||||
is HistoryVisibleEvent.Acknowledge -> coroutineScope.setAcknowledged(room.roomId, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return HistoryVisibleState(
|
|
||||||
showAlert = isFeatureEnabled && isHistoryVisible && roomInfo.isEncrypted == true && !acknowledged,
|
|
||||||
eventSink = ::handleEvent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.setAcknowledged(roomId: RoomId, value: Boolean) = launch {
|
|
||||||
repository.setAcknowledged(roomId, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
|
|
||||||
class HistoryVisibleStateProvider : PreviewParameterProvider<HistoryVisibleState> {
|
|
||||||
override val values: Sequence<HistoryVisibleState>
|
|
||||||
get() = sequenceOf(
|
|
||||||
aHistoryVisibleState(showAlert = true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun aHistoryVisibleState(
|
|
||||||
showAlert: Boolean = false,
|
|
||||||
eventSink: (HistoryVisibleEvent) -> Unit = {},
|
|
||||||
) = HistoryVisibleState(
|
|
||||||
showAlert,
|
|
||||||
eventSink = eventSink,
|
|
||||||
)
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import io.element.android.appconfig.LearnMoreConfig
|
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertLevel
|
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
import io.element.android.libraries.designsystem.text.stringWithLink
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun HistoryVisibleStateView(
|
|
||||||
state: HistoryVisibleState,
|
|
||||||
onLinkClick: (String, Boolean) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
if (!state.showAlert) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ComposerAlertMolecule(
|
|
||||||
modifier = modifier,
|
|
||||||
avatar = null,
|
|
||||||
showIcon = true,
|
|
||||||
level = ComposerAlertLevel.Info,
|
|
||||||
content = stringWithLink(
|
|
||||||
textRes = CommonStrings.crypto_history_visible,
|
|
||||||
url = LearnMoreConfig.HISTORY_VISIBLE_URL,
|
|
||||||
onLinkClick = { url -> onLinkClick(url, true) },
|
|
||||||
),
|
|
||||||
submitText = stringResource(CommonStrings.action_dismiss),
|
|
||||||
onSubmitClick = { state.eventSink(HistoryVisibleEvent.Acknowledge) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun HistoryVisibleStateViewPreview(
|
|
||||||
@PreviewParameter(HistoryVisibleStateProvider::class) state: HistoryVisibleState,
|
|
||||||
) = ElementPreview {
|
|
||||||
HistoryVisibleStateView(
|
|
||||||
state = state,
|
|
||||||
onLinkClick = { _, _ -> },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import io.element.android.features.messages.impl.MessagesView
|
|
||||||
import io.element.android.features.messages.impl.aMessagesState
|
|
||||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun MessagesViewWithHistoryVisiblePreview() = ElementPreview {
|
|
||||||
MessagesView(
|
|
||||||
state = aMessagesState(
|
|
||||||
composerState = aMessageComposerState(
|
|
||||||
textEditorState = aTextEditorStateMarkdown(
|
|
||||||
initialText = "",
|
|
||||||
initialFocus = false,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
historyVisibleState = aHistoryVisibleState(showAlert = true),
|
|
||||||
),
|
|
||||||
onBackClick = {},
|
|
||||||
onRoomDetailsClick = {},
|
|
||||||
onEventContentClick = { _, _ -> false },
|
|
||||||
onUserDataClick = {},
|
|
||||||
onLinkClick = { _, _ -> },
|
|
||||||
onSendLocationClick = {},
|
|
||||||
onCreatePollClick = {},
|
|
||||||
onJoinCallClick = {},
|
|
||||||
onViewAllPinnedMessagesClick = {},
|
|
||||||
knockRequestsBannerView = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -11,8 +11,6 @@ package io.element.android.features.messages.impl.di
|
||||||
import dev.zacsweers.metro.BindingContainer
|
import dev.zacsweers.metro.BindingContainer
|
||||||
import dev.zacsweers.metro.Binds
|
import dev.zacsweers.metro.Binds
|
||||||
import dev.zacsweers.metro.ContributesTo
|
import dev.zacsweers.metro.ContributesTo
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
|
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStatePresenter
|
|
||||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
||||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter
|
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter
|
||||||
|
|
@ -63,7 +61,4 @@ interface MessagesBindsModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter<IdentityChangeState>
|
fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter<IdentityChangeState>
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindHistoryVisibleStatePresenter(presenter: HistoryVisibleStatePresenter): Presenter<HistoryVisibleState>
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState
|
|
||||||
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
||||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||||
import io.element.android.features.messages.impl.link.aLinkState
|
import io.element.android.features.messages.impl.link.aLinkState
|
||||||
|
|
@ -1310,7 +1309,6 @@ class MessagesPresenterTest {
|
||||||
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
|
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
|
||||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||||
identityChangeStatePresenter = { anIdentityChangeState() },
|
identityChangeStatePresenter = { anIdentityChangeState() },
|
||||||
historyVisibleStatePresenter = { aHistoryVisibleState() },
|
|
||||||
linkPresenter = { aLinkState() },
|
linkPresenter = { aLinkState() },
|
||||||
actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
|
actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
|
||||||
customReactionPresenter = { aCustomReactionState() },
|
customReactionPresenter = { aCustomReactionState() },
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
|
|
||||||
class FakeHistoryVisibleAcknowledgementRepository(
|
|
||||||
private val acknowledgements: MutableMap<RoomId, MutableStateFlow<Boolean>> = mutableMapOf()
|
|
||||||
) : HistoryVisibleAcknowledgementRepository {
|
|
||||||
override fun hasAcknowledged(roomId: RoomId): Flow<Boolean> {
|
|
||||||
return acknowledgements.getOrPut(roomId) {
|
|
||||||
MutableStateFlow(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) {
|
|
||||||
val flow = acknowledgements.getOrPut(roomId) {
|
|
||||||
MutableStateFlow(value)
|
|
||||||
}
|
|
||||||
flow.emit(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Create the repository with a pre-existing entry.
|
|
||||||
*/
|
|
||||||
fun withRoom(roomId: RoomId, acknowledged: Boolean = false): FakeHistoryVisibleAcknowledgementRepository {
|
|
||||||
return FakeHistoryVisibleAcknowledgementRepository(
|
|
||||||
mutableMapOf(
|
|
||||||
roomId to MutableStateFlow(acknowledged)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.crypto.historyvisible
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
|
||||||
import io.element.android.tests.testutils.awaitLastSequentialItem
|
|
||||||
import io.element.android.tests.testutils.test
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class HistoryVisibleStatePresenterTest {
|
|
||||||
@get:Rule
|
|
||||||
val warmUpRule = WarmUpRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - not visible if feature disabled`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
|
|
||||||
val presenter = createHistoryVisibleStatePresenter(room, enabled = false, acknowledged = false)
|
|
||||||
presenter.test {
|
|
||||||
assertThat(awaitLastSequentialItem().showAlert).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial with room shared, unencrypted`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = false))
|
|
||||||
val presenter = createHistoryVisibleStatePresenter(room)
|
|
||||||
presenter.test {
|
|
||||||
assertThat(awaitLastSequentialItem().showAlert).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial with room joined, encrypted`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true))
|
|
||||||
val presenter = createHistoryVisibleStatePresenter(room)
|
|
||||||
presenter.test {
|
|
||||||
assertThat(awaitLastSequentialItem().showAlert).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial with room invited, encrypted`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Invited, isEncrypted = true))
|
|
||||||
val presenter = createHistoryVisibleStatePresenter(room)
|
|
||||||
presenter.test {
|
|
||||||
assertThat(awaitLastSequentialItem().showAlert).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial with room shared, encrypted, unacknowledged`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
|
|
||||||
val presenter = createHistoryVisibleStatePresenter(room, acknowledged = false)
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.showAlert).isFalse()
|
|
||||||
val nextState = awaitItem()
|
|
||||||
assertThat(nextState.showAlert).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial with room shared, encrypted, acknowledged`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
|
|
||||||
val presenter = createHistoryVisibleStatePresenter(room, acknowledged = true)
|
|
||||||
presenter.test {
|
|
||||||
assertThat(awaitLastSequentialItem().showAlert).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - transition from joined + unencrypted, to shared + encrypted`() = runTest {
|
|
||||||
val room = FakeJoinedRoom()
|
|
||||||
val featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true))
|
|
||||||
val repository = FakeHistoryVisibleAcknowledgementRepository()
|
|
||||||
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false))
|
|
||||||
|
|
||||||
val presenter = HistoryVisibleStatePresenter(
|
|
||||||
featureFlagService,
|
|
||||||
repository,
|
|
||||||
room,
|
|
||||||
)
|
|
||||||
|
|
||||||
presenter.test {
|
|
||||||
// emitted by the feature flag service(?)
|
|
||||||
assertThat(awaitItem().showAlert).isFalse()
|
|
||||||
|
|
||||||
// emitted state from room info assignment
|
|
||||||
assertThat(awaitItem().showAlert).isFalse()
|
|
||||||
|
|
||||||
// room is marked as encrypted
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true))
|
|
||||||
assertThat(awaitItem().showAlert).isFalse()
|
|
||||||
|
|
||||||
// room history visibility is changed to shared
|
|
||||||
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
|
|
||||||
assertThat(awaitItem().showAlert).isTrue()
|
|
||||||
|
|
||||||
// alert is acknowledged
|
|
||||||
repository.setAcknowledged(room.roomId, true)
|
|
||||||
assertThat(awaitItem().showAlert).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createHistoryVisibleStatePresenter(
|
|
||||||
room: JoinedRoom = FakeJoinedRoom(),
|
|
||||||
enabled: Boolean = true,
|
|
||||||
acknowledged: Boolean = false
|
|
||||||
): HistoryVisibleStatePresenter {
|
|
||||||
return HistoryVisibleStatePresenter(
|
|
||||||
room = room,
|
|
||||||
featureFlagService = FakeFeatureFlagService(mapOf("feature.enableKeyShareOnInvite" to enabled)),
|
|
||||||
repository = FakeHistoryVisibleAcknowledgementRepository.withRoom(room.roomId, acknowledged)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -101,7 +101,6 @@ class KonsistPreviewTest {
|
||||||
"MessageComposerViewVoicePreview",
|
"MessageComposerViewVoicePreview",
|
||||||
"MessagesReactionButtonAddPreview",
|
"MessagesReactionButtonAddPreview",
|
||||||
"MessagesReactionButtonExtraPreview",
|
"MessagesReactionButtonExtraPreview",
|
||||||
"MessagesViewWithHistoryVisiblePreview",
|
|
||||||
"MessagesViewWithIdentityChangePreview",
|
"MessagesViewWithIdentityChangePreview",
|
||||||
"PendingMemberRowWithLongNamePreview",
|
"PendingMemberRowWithLongNamePreview",
|
||||||
"PinUnlockViewInAppPreview",
|
"PinUnlockViewInAppPreview",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:0489a15e6f8b358780484ea33807aaa2079512f7077b81b1e7e455092968cd61
|
|
||||||
size 25463
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:da4fa289d3a4f32da964a47392458a09bdf80d4410e9ab9519225d077bb4a168
|
|
||||||
size 28316
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:cf7b2508bdf41e2ee9b8b26207c604232f54314797237aceec5675524a7c8ab5
|
|
||||||
size 66644
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:e58f097893ebc5f8664283d3e22b055b7294ce6d6bf571b34507e2242f70e677
|
|
||||||
size 68988
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:47c0b2a5dedb5b8ca1a027dfe93b27b97069f4c65b9e1553d7847f5f06348678
|
|
||||||
size 68593
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:e0581c343abffc4a71db827cdc5f8b183525daed8b17b80cd6b511cb70ae9a05
|
|
||||||
size 66428
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:1ea74e00b157d5e0f7caf71ba839f1523bfbc0684335ba270aec05187a88c3e5
|
|
||||||
size 70237
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:c5d618df3faa09281e59897b7f478d12f5a0d907b47f58747607e1de8cbc6e61
|
|
||||||
size 68146
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue