Merge pull request #2349 from element-hq/feature/bma/disableTyping
"Share presence" setting
This commit is contained in:
commit
022d309eb6
35 changed files with 197 additions and 58 deletions
2
changelog.d/2241.feature
Normal file
2
changelog.d/2241.feature
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Change "Read receipts" advanced setting used to send private Read Receipt to "Share presence" settings.
|
||||
When disabled, private Read Receipts will be sent, and no typing notification will be sent. Also Read Receipts and typing notifications will not be rendered in the timeline.
|
||||
|
|
@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -37,6 +38,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
|
|||
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
|
||||
import io.element.android.features.messages.impl.mentions.MentionSuggestion
|
||||
import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
|
|
@ -86,6 +88,7 @@ class MessageComposerPresenter @Inject constructor(
|
|||
private val room: MatrixRoom,
|
||||
private val mediaPickerProvider: PickerProvider,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val localMediaFactory: LocalMediaFactory,
|
||||
private val mediaSender: MediaSender,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
|
|
@ -147,6 +150,8 @@ class MessageComposerPresenter @Inject constructor(
|
|||
var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) }
|
||||
var showTextFormatting: Boolean by remember { mutableStateOf(false) }
|
||||
|
||||
val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true)
|
||||
|
||||
LaunchedEffect(messageComposerContext.composerMode) {
|
||||
when (val modeValue = messageComposerContext.composerMode) {
|
||||
is MessageComposerMode.Edit ->
|
||||
|
|
@ -212,7 +217,9 @@ class MessageComposerPresenter @Inject constructor(
|
|||
// Declare that the user is not typing anymore when the composer is disposed
|
||||
onDispose {
|
||||
appCoroutineScope.launch {
|
||||
room.typingNotice(false)
|
||||
if (sendTypingNotifications) {
|
||||
room.typingNotice(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -310,8 +317,10 @@ class MessageComposerPresenter @Inject constructor(
|
|||
analyticsService.trackError(event.error)
|
||||
}
|
||||
is MessageComposerEvents.TypingNotice -> {
|
||||
localCoroutineScope.launch {
|
||||
room.typingNotice(event.isTyping)
|
||||
if (sendTypingNotifications) {
|
||||
localCoroutineScope.launch {
|
||||
room.typingNotice(event.isTyping)
|
||||
}
|
||||
}
|
||||
}
|
||||
is MessageComposerEvents.SuggestionReceived -> {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
val keyBackupState by encryptionService.backupStateStateFlow.collectAsState()
|
||||
|
||||
val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true)
|
||||
val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true)
|
||||
|
||||
val sessionState by remember {
|
||||
derivedStateOf {
|
||||
|
|
@ -183,6 +184,7 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
highlightedEventId = highlightedEventId.value,
|
||||
paginationState = paginationState,
|
||||
timelineItems = timelineItems,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
newEventState = newItemState.value,
|
||||
sessionState = sessionState,
|
||||
eventSink = { handleEvents(it) }
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
data class TimelineState(
|
||||
val timelineItems: ImmutableList<TimelineItem>,
|
||||
val timelineRoomInfo: TimelineRoomInfo,
|
||||
val renderReadReceipts: Boolean,
|
||||
val highlightedEventId: EventId?,
|
||||
val paginationState: MatrixTimeline.PaginationState,
|
||||
val newEventState: NewEventState,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import kotlin.random.Random
|
|||
fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf()) = TimelineState(
|
||||
timelineItems = timelineItems,
|
||||
timelineRoomInfo = aTimelineRoomInfo(),
|
||||
renderReadReceipts = false,
|
||||
paginationState = MatrixTimeline.PaginationState(
|
||||
isBackPaginating = false,
|
||||
hasMoreToLoadBackwards = true,
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ fun TimelineView(
|
|||
TimelineItemRow(
|
||||
timelineItem = timelineItem,
|
||||
timelineRoomInfo = state.timelineRoomInfo,
|
||||
renderReadReceipts = state.renderReadReceipts,
|
||||
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true &&
|
||||
state.timelineItems.first().identifier() == timelineItem.identifier(),
|
||||
highlightedItem = state.highlightedEventId?.value,
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
|||
internal fun ATimelineItemEventRow(
|
||||
event: TimelineItem.Event,
|
||||
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
|
||||
renderReadReceipts: Boolean = false,
|
||||
isLastOutgoingMessage: Boolean = false,
|
||||
isHighlighted: Boolean = false,
|
||||
) = TimelineItemEventRow(
|
||||
event = event,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = isHighlighted,
|
||||
onClick = {},
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ import kotlin.math.roundToInt
|
|||
fun TimelineItemEventRow(
|
||||
event: TimelineItem.Event,
|
||||
timelineRoomInfo: TimelineRoomInfo,
|
||||
renderReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
isHighlighted: Boolean,
|
||||
onClick: () -> Unit,
|
||||
|
|
@ -223,6 +224,7 @@ fun TimelineItemEventRow(
|
|||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
receipts = event.readReceiptState.receipts,
|
||||
),
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
onReadReceiptsClicked = { onReadReceiptClick(event) },
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ internal fun TimelineItemEventRowWithRRPreview(
|
|||
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||
readReceiptState = TimelineItemReadReceipts(state.receipts),
|
||||
),
|
||||
renderReadReceipts = true,
|
||||
isLastOutgoingMessage = false,
|
||||
)
|
||||
// A message from current user
|
||||
|
|
@ -60,6 +61,7 @@ internal fun TimelineItemEventRowWithRRPreview(
|
|||
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||
readReceiptState = TimelineItemReadReceipts(state.receipts),
|
||||
),
|
||||
renderReadReceipts = true,
|
||||
isLastOutgoingMessage = false,
|
||||
)
|
||||
// Another message from current user
|
||||
|
|
@ -73,6 +75,7 @@ internal fun TimelineItemEventRowWithRRPreview(
|
|||
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||
readReceiptState = TimelineItemReadReceipts(state.receipts),
|
||||
),
|
||||
renderReadReceipts = true,
|
||||
isLastOutgoingMessage = true,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
fun TimelineItemGroupedEventsRow(
|
||||
timelineItem: TimelineItem.GroupedEvents,
|
||||
timelineRoomInfo: TimelineRoomInfo,
|
||||
renderReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
highlightedItem: String?,
|
||||
sessionState: SessionState,
|
||||
|
|
@ -70,6 +71,7 @@ fun TimelineItemGroupedEventsRow(
|
|||
timelineItem = timelineItem,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
highlightedItem = highlightedItem,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
sessionState = sessionState,
|
||||
onClick = onClick,
|
||||
|
|
@ -93,6 +95,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||
timelineItem: TimelineItem.GroupedEvents,
|
||||
timelineRoomInfo: TimelineRoomInfo,
|
||||
highlightedItem: String?,
|
||||
renderReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
sessionState: SessionState,
|
||||
onClick: (TimelineItem.Event) -> Unit,
|
||||
|
|
@ -124,6 +127,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||
TimelineItemRow(
|
||||
timelineItem = subGroupEvent,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
highlightedItem = highlightedItem,
|
||||
sessionState = sessionState,
|
||||
|
|
@ -141,13 +145,14 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (renderReadReceipts) {
|
||||
TimelineItemReadReceiptView(
|
||||
state = ReadReceiptViewState(
|
||||
sendState = null,
|
||||
isLastOutgoingMessage = false,
|
||||
receipts = timelineItem.aggregatedReadReceipts,
|
||||
),
|
||||
renderReadReceipts = true,
|
||||
onReadReceiptsClicked = onExpandGroupClick
|
||||
)
|
||||
}
|
||||
|
|
@ -163,6 +168,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
|
|||
timelineItem = aGroupedEvents(withReadReceipts = true),
|
||||
timelineRoomInfo = aTimelineRoomInfo(),
|
||||
highlightedItem = null,
|
||||
renderReadReceipts = true,
|
||||
isLastOutgoingMessage = false,
|
||||
sessionState = aSessionState(),
|
||||
onClick = {},
|
||||
|
|
@ -187,6 +193,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
|
|||
timelineItem = aGroupedEvents(withReadReceipts = true),
|
||||
timelineRoomInfo = aTimelineRoomInfo(),
|
||||
highlightedItem = null,
|
||||
renderReadReceipts = true,
|
||||
isLastOutgoingMessage = false,
|
||||
sessionState = aSessionState(),
|
||||
onClick = {},
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
internal fun TimelineItemRow(
|
||||
timelineItem: TimelineItem,
|
||||
timelineRoomInfo: TimelineRoomInfo,
|
||||
renderReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
highlightedItem: String?,
|
||||
sessionState: SessionState,
|
||||
|
|
@ -58,6 +59,7 @@ internal fun TimelineItemRow(
|
|||
if (timelineItem.content is TimelineItemStateContent) {
|
||||
TimelineItemStateEventRow(
|
||||
event = timelineItem,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||
onClick = { onClick(timelineItem) },
|
||||
|
|
@ -70,6 +72,7 @@ internal fun TimelineItemRow(
|
|||
TimelineItemEventRow(
|
||||
event = timelineItem,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
isHighlighted = highlightedItem == timelineItem.identifier(),
|
||||
onClick = { onClick(timelineItem) },
|
||||
|
|
@ -91,6 +94,7 @@ internal fun TimelineItemRow(
|
|||
TimelineItemGroupedEventsRow(
|
||||
timelineItem = timelineItem,
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
highlightedItem = highlightedItem,
|
||||
sessionState = sessionState,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
@Composable
|
||||
fun TimelineItemStateEventRow(
|
||||
event: TimelineItem.Event,
|
||||
renderReadReceipts: Boolean,
|
||||
isLastOutgoingMessage: Boolean,
|
||||
isHighlighted: Boolean,
|
||||
onClick: () -> Unit,
|
||||
|
|
@ -90,6 +91,7 @@ fun TimelineItemStateEventRow(
|
|||
isLastOutgoingMessage = isLastOutgoingMessage,
|
||||
receipts = event.readReceiptState.receipts,
|
||||
),
|
||||
renderReadReceipts = renderReadReceipts,
|
||||
onReadReceiptsClicked = { onReadReceiptsClick(event) },
|
||||
)
|
||||
}
|
||||
|
|
@ -107,6 +109,7 @@ internal fun TimelineItemStateEventRowPreview() = ElementPreview {
|
|||
receipts = listOf(aReadReceiptData(0)).toPersistentList(),
|
||||
)
|
||||
),
|
||||
renderReadReceipts = true,
|
||||
isLastOutgoingMessage = false,
|
||||
isHighlighted = false,
|
||||
onClick = {},
|
||||
|
|
|
|||
|
|
@ -58,20 +58,23 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
@Composable
|
||||
fun TimelineItemReadReceiptView(
|
||||
state: ReadReceiptViewState,
|
||||
renderReadReceipts: Boolean,
|
||||
onReadReceiptsClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (state.receipts.isNotEmpty()) {
|
||||
ReadReceiptsRow(modifier = modifier) {
|
||||
ReadReceiptsAvatars(
|
||||
receipts = state.receipts,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.clickable {
|
||||
onReadReceiptsClicked()
|
||||
}
|
||||
.padding(2.dp)
|
||||
)
|
||||
if (renderReadReceipts) {
|
||||
ReadReceiptsRow(modifier = modifier) {
|
||||
ReadReceiptsAvatars(
|
||||
receipts = state.receipts,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.clickable {
|
||||
onReadReceiptsClicked()
|
||||
}
|
||||
.padding(2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (state.sendState) {
|
||||
|
|
@ -206,6 +209,7 @@ internal fun TimelineItemReactionsViewPreview(
|
|||
) = ElementPreview {
|
||||
TimelineItemReadReceiptView(
|
||||
state = state,
|
||||
renderReadReceipts = true,
|
||||
onReadReceiptsClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -675,6 +675,7 @@ class MessagesPresenterTest {
|
|||
room = matrixRoom,
|
||||
mediaPickerProvider = FakePickerProvider(),
|
||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.NotificationSettings.key to true)),
|
||||
sessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
localMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
|
||||
mediaSender = mediaSender,
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
|
|
|
|||
|
|
@ -32,11 +32,13 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
|
|||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
|
|
@ -888,6 +890,24 @@ class MessageComposerPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle typing notice event when sending typing notice is disabled`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val store = InMemorySessionPreferencesStore(
|
||||
isSendTypingNotificationsEnabled = false
|
||||
)
|
||||
val presenter = createPresenter(room = room, sessionPreferencesStore = store, coroutineScope = this)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(room.typingRecord).isEmpty()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
|
||||
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
|
||||
assertThat(room.typingRecord).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ReceiveTurbine<MessageComposerState>.backToNormalMode(state: MessageComposerState, skipCount: Int = 0): MessageComposerState {
|
||||
state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode)
|
||||
skipItems(skipCount)
|
||||
|
|
@ -901,6 +921,7 @@ class MessageComposerPresenterTest {
|
|||
room: MatrixRoom = FakeMatrixRoom(),
|
||||
pickerProvider: PickerProvider = this.pickerProvider,
|
||||
featureFlagService: FeatureFlagService = this.featureFlagService,
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor,
|
||||
snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher,
|
||||
permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
|
|
@ -909,6 +930,7 @@ class MessageComposerPresenterTest {
|
|||
room,
|
||||
pickerProvider,
|
||||
featureFlagService,
|
||||
sessionPreferencesStore,
|
||||
localMediaFactory,
|
||||
MediaSender(mediaPreProcessor, room),
|
||||
snackbarDispatcher,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.compound.theme.Theme
|
|||
sealed interface AdvancedSettingsEvents {
|
||||
data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
||||
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
||||
data class SetSendPublicReadReceiptsEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
||||
data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
||||
data object ChangeTheme : AdvancedSettingsEvents
|
||||
data object CancelChangeTheme : AdvancedSettingsEvents
|
||||
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ class AdvancedSettingsPresenter @Inject constructor(
|
|||
val isDeveloperModeEnabled by appPreferencesStore
|
||||
.isDeveloperModeEnabledFlow()
|
||||
.collectAsState(initial = false)
|
||||
val isSendPublicReadReceiptsEnabled by sessionPreferencesStore
|
||||
.isSendPublicReadReceiptsEnabled()
|
||||
val isSharePresenceEnabled by sessionPreferencesStore
|
||||
.isSharePresenceEnabled()
|
||||
.collectAsState(initial = true)
|
||||
val theme by remember {
|
||||
appPreferencesStore.getThemeFlow().mapToTheme()
|
||||
|
|
@ -60,8 +60,8 @@ class AdvancedSettingsPresenter @Inject constructor(
|
|||
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
|
||||
appPreferencesStore.setDeveloperModeEnabled(event.enabled)
|
||||
}
|
||||
is AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled -> localCoroutineScope.launch {
|
||||
sessionPreferencesStore.setSendPublicReadReceipts(event.enabled)
|
||||
is AdvancedSettingsEvents.SetSharePresenceEnabled -> localCoroutineScope.launch {
|
||||
sessionPreferencesStore.setSharePresence(event.enabled)
|
||||
}
|
||||
AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false
|
||||
AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true
|
||||
|
|
@ -75,7 +75,7 @@ class AdvancedSettingsPresenter @Inject constructor(
|
|||
return AdvancedSettingsState(
|
||||
isRichTextEditorEnabled = isRichTextEditorEnabled,
|
||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||
isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled,
|
||||
isSharePresenceEnabled = isSharePresenceEnabled,
|
||||
theme = theme,
|
||||
showChangeThemeDialog = showChangeThemeDialog,
|
||||
eventSink = { handleEvents(it) }
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.compound.theme.Theme
|
|||
data class AdvancedSettingsState(
|
||||
val isRichTextEditorEnabled: Boolean,
|
||||
val isDeveloperModeEnabled: Boolean,
|
||||
val isSendPublicReadReceiptsEnabled: Boolean,
|
||||
val isSharePresenceEnabled: Boolean,
|
||||
val theme: Theme,
|
||||
val showChangeThemeDialog: Boolean,
|
||||
val eventSink: (AdvancedSettingsEvents) -> Unit
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ fun aAdvancedSettingsState(
|
|||
) = AdvancedSettingsState(
|
||||
isRichTextEditorEnabled = isRichTextEditorEnabled,
|
||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||
isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled,
|
||||
isSharePresenceEnabled = isSendPublicReadReceiptsEnabled,
|
||||
theme = Theme.System,
|
||||
showChangeThemeDialog = showChangeThemeDialog,
|
||||
eventSink = {}
|
||||
|
|
|
|||
|
|
@ -83,15 +83,15 @@ fun AdvancedSettingsView(
|
|||
)
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(text = stringResource(id = R.string.screen_advanced_settings_send_read_receipts))
|
||||
Text(text = stringResource(id = R.string.screen_advanced_settings_share_presence))
|
||||
},
|
||||
supportingContent = {
|
||||
Text(text = stringResource(id = R.string.screen_advanced_settings_send_read_receipts_description))
|
||||
Text(text = stringResource(id = R.string.screen_advanced_settings_share_presence_description))
|
||||
},
|
||||
trailingContent = ListItemContent.Switch(
|
||||
checked = state.isSendPublicReadReceiptsEnabled,
|
||||
checked = state.isSharePresenceEnabled,
|
||||
),
|
||||
onClick = { state.eventSink(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(!state.isSendPublicReadReceiptsEnabled)) }
|
||||
onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
<string name="screen_advanced_settings_rich_text_editor_description">"Disable the rich text editor to type Markdown manually."</string>
|
||||
<string name="screen_advanced_settings_send_read_receipts">"Read receipts"</string>
|
||||
<string name="screen_advanced_settings_send_read_receipts_description">"If turned off, your read receipts won\'t be sent to anyone. You will still receive read receipts from other users."</string>
|
||||
<string name="screen_advanced_settings_share_presence">"Share presence"</string>
|
||||
<string name="screen_advanced_settings_share_presence_description">"If turned off, you won’t be able to send or receive read receipts or typing notifications"</string>
|
||||
<string name="screen_advanced_settings_view_source_description">"Enable option to view message source in the timeline."</string>
|
||||
<string name="screen_edit_profile_display_name">"Display name"</string>
|
||||
<string name="screen_edit_profile_display_name_placeholder">"Your display name"</string>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class AdvancedSettingsPresenterTest {
|
|||
assertThat(initialState.isDeveloperModeEnabled).isFalse()
|
||||
assertThat(initialState.isRichTextEditorEnabled).isFalse()
|
||||
assertThat(initialState.showChangeThemeDialog).isFalse()
|
||||
assertThat(initialState.isSendPublicReadReceiptsEnabled).isTrue()
|
||||
assertThat(initialState.isSharePresenceEnabled).isTrue()
|
||||
assertThat(initialState.theme).isEqualTo(Theme.System)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,17 +79,17 @@ class AdvancedSettingsPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - send public read receipts off on`() = runTest {
|
||||
fun `present - share presence off on`() = runTest {
|
||||
val presenter = createAdvancedSettingsPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitLastSequentialItem()
|
||||
assertThat(initialState.isSendPublicReadReceiptsEnabled).isTrue()
|
||||
initialState.eventSink.invoke(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(false))
|
||||
assertThat(awaitItem().isSendPublicReadReceiptsEnabled).isFalse()
|
||||
initialState.eventSink.invoke(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(true))
|
||||
assertThat(awaitItem().isSendPublicReadReceiptsEnabled).isTrue()
|
||||
assertThat(initialState.isSharePresenceEnabled).isTrue()
|
||||
initialState.eventSink.invoke(AdvancedSettingsEvents.SetSharePresenceEnabled(false))
|
||||
assertThat(awaitItem().isSharePresenceEnabled).isFalse()
|
||||
initialState.eventSink.invoke(AdvancedSettingsEvents.SetSharePresenceEnabled(true))
|
||||
assertThat(awaitItem().isSharePresenceEnabled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,20 @@ package io.element.android.features.preferences.api.store
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SessionPreferencesStore {
|
||||
suspend fun setSharePresence(enabled: Boolean)
|
||||
fun isSharePresenceEnabled(): Flow<Boolean>
|
||||
|
||||
suspend fun setSendPublicReadReceipts(enabled: Boolean)
|
||||
fun isSendPublicReadReceiptsEnabled(): Flow<Boolean>
|
||||
|
||||
suspend fun setRenderReadReceipts(enabled: Boolean)
|
||||
fun isRenderReadReceiptsEnabled(): Flow<Boolean>
|
||||
|
||||
suspend fun setSendTypingNotifications(enabled: Boolean)
|
||||
fun isSendTypingNotificationsEnabled(): Flow<Boolean>
|
||||
|
||||
suspend fun setRenderTypingNotifications(enabled: Boolean)
|
||||
fun isRenderTypingNotificationsEnabled(): Flow<Boolean>
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
|||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
|
||||
class DefaultSessionPreferencesStore(
|
||||
|
|
@ -43,13 +45,41 @@ class DefaultSessionPreferencesStore(
|
|||
return context.preferencesDataStoreFile("session_${hashedUserId}_preferences")
|
||||
}
|
||||
}
|
||||
|
||||
private val sharePresenceKey = booleanPreferencesKey("sharePresence")
|
||||
private val sendPublicReadReceiptsKey = booleanPreferencesKey("sendPublicReadReceipts")
|
||||
private val renderReadReceiptsKey = booleanPreferencesKey("renderReadReceipts")
|
||||
private val sendTypingNotificationsKey = booleanPreferencesKey("sendTypingNotifications")
|
||||
private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications")
|
||||
|
||||
private val dataStoreFile = storeFile(context, sessionId)
|
||||
private val store = PreferenceDataStoreFactory.create(scope = sessionCoroutineScope) { dataStoreFile }
|
||||
|
||||
override suspend fun setSharePresence(enabled: Boolean) {
|
||||
update(sharePresenceKey, enabled)
|
||||
// Also update all the other settings
|
||||
setSendPublicReadReceipts(enabled)
|
||||
setRenderReadReceipts(enabled)
|
||||
setSendTypingNotifications(enabled)
|
||||
setRenderTypingNotifications(enabled)
|
||||
}
|
||||
|
||||
override fun isSharePresenceEnabled(): Flow<Boolean> {
|
||||
// Migration, if sendPublicReadReceiptsKey was false, consider that sharing presence is false.
|
||||
return get(sharePresenceKey) { runBlocking { isSendPublicReadReceiptsEnabled().first() } }
|
||||
}
|
||||
|
||||
override suspend fun setSendPublicReadReceipts(enabled: Boolean) = update(sendPublicReadReceiptsKey, enabled)
|
||||
override fun isSendPublicReadReceiptsEnabled(): Flow<Boolean> = get(sendPublicReadReceiptsKey, true)
|
||||
override fun isSendPublicReadReceiptsEnabled(): Flow<Boolean> = get(sendPublicReadReceiptsKey) { true }
|
||||
|
||||
override suspend fun setRenderReadReceipts(enabled: Boolean) = update(renderReadReceiptsKey, enabled)
|
||||
override fun isRenderReadReceiptsEnabled(): Flow<Boolean> = get(renderReadReceiptsKey) { true }
|
||||
|
||||
override suspend fun setSendTypingNotifications(enabled: Boolean) = update(sendTypingNotificationsKey, enabled)
|
||||
override fun isSendTypingNotificationsEnabled(): Flow<Boolean> = get(sendTypingNotificationsKey) { true }
|
||||
|
||||
override suspend fun setRenderTypingNotifications(enabled: Boolean) = update(renderTypingNotificationsKey, enabled)
|
||||
override fun isRenderTypingNotificationsEnabled(): Flow<Boolean> = get(renderTypingNotificationsKey) { true }
|
||||
|
||||
override suspend fun clear() {
|
||||
dataStoreFile.safeDelete()
|
||||
|
|
@ -59,7 +89,7 @@ class DefaultSessionPreferencesStore(
|
|||
store.edit { prefs -> prefs[key] = value }
|
||||
}
|
||||
|
||||
private fun <T> get(key: Preferences.Key<T>, default: T): Flow<T> {
|
||||
return store.data.map { prefs -> prefs[key] ?: default }
|
||||
private fun <T> get(key: Preferences.Key<T>, default: () -> T): Flow<T> {
|
||||
return store.data.map { prefs -> prefs[key] ?: default() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,19 +21,50 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class InMemorySessionPreferencesStore(
|
||||
isSharePresenceEnabled: Boolean = true,
|
||||
isSendPublicReadReceiptsEnabled: Boolean = true,
|
||||
isRenderReadReceiptsEnabled: Boolean = true,
|
||||
isSendTypingNotificationsEnabled: Boolean = true,
|
||||
isRenderTypingNotificationsEnabled: Boolean = true,
|
||||
) : SessionPreferencesStore {
|
||||
private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled)
|
||||
private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled)
|
||||
private val isRenderReadReceiptsEnabled = MutableStateFlow(isRenderReadReceiptsEnabled)
|
||||
private val isSendTypingNotificationsEnabled = MutableStateFlow(isSendTypingNotificationsEnabled)
|
||||
private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled)
|
||||
var clearCallCount = 0
|
||||
private set
|
||||
|
||||
override suspend fun setSharePresence(enabled: Boolean) {
|
||||
isSharePresenceEnabled.tryEmit(enabled)
|
||||
}
|
||||
|
||||
override fun isSharePresenceEnabled(): Flow<Boolean> = isSharePresenceEnabled
|
||||
|
||||
override suspend fun setSendPublicReadReceipts(enabled: Boolean) {
|
||||
isSendPublicReadReceiptsEnabled.tryEmit(enabled)
|
||||
}
|
||||
override fun isSendPublicReadReceiptsEnabled(): Flow<Boolean> {
|
||||
return isSendPublicReadReceiptsEnabled
|
||||
|
||||
override fun isSendPublicReadReceiptsEnabled(): Flow<Boolean> = isSendPublicReadReceiptsEnabled
|
||||
|
||||
override suspend fun setRenderReadReceipts(enabled: Boolean) {
|
||||
isRenderReadReceiptsEnabled.tryEmit(enabled)
|
||||
}
|
||||
|
||||
override fun isRenderReadReceiptsEnabled(): Flow<Boolean> = isRenderReadReceiptsEnabled
|
||||
|
||||
override suspend fun setSendTypingNotifications(enabled: Boolean) {
|
||||
isSendTypingNotificationsEnabled.tryEmit(enabled)
|
||||
}
|
||||
|
||||
override fun isSendTypingNotificationsEnabled(): Flow<Boolean> = isSendTypingNotificationsEnabled
|
||||
|
||||
override suspend fun setRenderTypingNotifications(enabled: Boolean) {
|
||||
isRenderTypingNotificationsEnabled.tryEmit(enabled)
|
||||
}
|
||||
|
||||
override fun isRenderTypingNotificationsEnabled(): Flow<Boolean> = isRenderTypingNotificationsEnabled
|
||||
|
||||
override suspend fun clear() {
|
||||
clearCallCount++
|
||||
isSendPublicReadReceiptsEnabled.tryEmit(true)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:da3f8614862ddacfd9b54afcd3bf091bec335e1489d7cbffc826059a89ae5478
|
||||
size 58453
|
||||
oid sha256:41d892f8a97bb9d89d6a6128c7acae4d5c509dadee0e1758c7fec9d76bc4216d
|
||||
size 56386
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1fd599f2e93432d5d5555f086b60059e84452a0da3c2a69d932042b7ee4d3bac
|
||||
size 57922
|
||||
oid sha256:478e8c2d3dd29baf88f2641813760c623631d53446f31bca2f1372c05d4ca914
|
||||
size 55856
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e9641228e0482c8e5500bb999b180e79cbfcb6a13e121f3be68aae5c58a6839
|
||||
size 57953
|
||||
oid sha256:6e174f5f0b15343f5f02cc716cb371aa89d87e298104ac8e5df83bdab2db573d
|
||||
size 55887
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1c1c5b00098e5860d4c69ef9a652483ea1192d9fa10f5449fb817769ab054183
|
||||
size 36128
|
||||
oid sha256:d044628791909fdb8f621b3693cb63726e6a1dbc1fd6e12e1b3a4e3c9be1e868
|
||||
size 36288
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4fe6625641cf06c45a817e9d10bc0d7296aa59160fc5a3e9dc28304e5b794125
|
||||
size 57943
|
||||
oid sha256:62762b55b56ee307e15a2aa488e8426714771d6e9d76d68ee82185b3420a348f
|
||||
size 55831
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e85b252d0d6c166f987974ebcf9a6b58b924316733b868ad0ed5a61413bda381
|
||||
size 54577
|
||||
oid sha256:923619c9af298ce1a250c5094429b6b820b560c62a55ec8baa40144478e42358
|
||||
size 53102
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4f246ce9f60b25d9739d73e2aed98132dfb3a0a694acbf320ff796f35f805fdc
|
||||
size 54273
|
||||
oid sha256:1125869fdf539f15103bef1a380e46af9b56c44262a1986009099e2700206038
|
||||
size 52799
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d0403a4bb8da3d36922cb5362977af9b3052624525def05e5236f4f922384c31
|
||||
size 54297
|
||||
oid sha256:ecee6cd60257a931f717c8ae23f256ded8dd6ac8fa7af7430082c6a3f837b274
|
||||
size 52821
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:824774a1e19974090de4cb32e06a7f984b5db238c3dc8d04a35d3457e647ac84
|
||||
size 31999
|
||||
oid sha256:8449c20314175d6e829df65b2b1ba3cf36944a679b2e360ee5d2ed4d8333ad07
|
||||
size 32149
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c0820b462a0dc103b66aef78528ae5ce13a55e2614a7a197e06713366c422893
|
||||
size 54313
|
||||
oid sha256:5c38a6f66d48174c6e2b3ac8525906cd371030f15ac3ca8727a8ca83f528916e
|
||||
size 52823
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue