Merge pull request #2349 from element-hq/feature/bma/disableTyping

"Share presence" setting
This commit is contained in:
Benoit Marty 2024-02-07 10:29:43 +01:00 committed by GitHub
commit 022d309eb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 197 additions and 58 deletions

2
changelog.d/2241.feature Normal file
View 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.

View file

@ -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 -> {

View file

@ -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) }

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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 = {},

View file

@ -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),
)

View file

@ -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,
)
}

View file

@ -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 = {},

View file

@ -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,

View file

@ -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 = {},

View file

@ -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 = {},
)
}

View file

@ -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(),

View file

@ -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,

View file

@ -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

View file

@ -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) }

View file

@ -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

View file

@ -38,7 +38,7 @@ fun aAdvancedSettingsState(
) = AdvancedSettingsState(
isRichTextEditorEnabled = isRichTextEditorEnabled,
isDeveloperModeEnabled = isDeveloperModeEnabled,
isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled,
isSharePresenceEnabled = isSendPublicReadReceiptsEnabled,
theme = Theme.System,
showChangeThemeDialog = showChangeThemeDialog,
eventSink = {}

View file

@ -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)) }
)
}

View file

@ -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 wont 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>

View file

@ -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()
}
}

View file

@ -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()
}

View file

@ -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() }
}
}

View file

@ -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)

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da3f8614862ddacfd9b54afcd3bf091bec335e1489d7cbffc826059a89ae5478
size 58453
oid sha256:41d892f8a97bb9d89d6a6128c7acae4d5c509dadee0e1758c7fec9d76bc4216d
size 56386

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1fd599f2e93432d5d5555f086b60059e84452a0da3c2a69d932042b7ee4d3bac
size 57922
oid sha256:478e8c2d3dd29baf88f2641813760c623631d53446f31bca2f1372c05d4ca914
size 55856

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e9641228e0482c8e5500bb999b180e79cbfcb6a13e121f3be68aae5c58a6839
size 57953
oid sha256:6e174f5f0b15343f5f02cc716cb371aa89d87e298104ac8e5df83bdab2db573d
size 55887

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c1c5b00098e5860d4c69ef9a652483ea1192d9fa10f5449fb817769ab054183
size 36128
oid sha256:d044628791909fdb8f621b3693cb63726e6a1dbc1fd6e12e1b3a4e3c9be1e868
size 36288

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4fe6625641cf06c45a817e9d10bc0d7296aa59160fc5a3e9dc28304e5b794125
size 57943
oid sha256:62762b55b56ee307e15a2aa488e8426714771d6e9d76d68ee82185b3420a348f
size 55831

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e85b252d0d6c166f987974ebcf9a6b58b924316733b868ad0ed5a61413bda381
size 54577
oid sha256:923619c9af298ce1a250c5094429b6b820b560c62a55ec8baa40144478e42358
size 53102

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f246ce9f60b25d9739d73e2aed98132dfb3a0a694acbf320ff796f35f805fdc
size 54273
oid sha256:1125869fdf539f15103bef1a380e46af9b56c44262a1986009099e2700206038
size 52799

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d0403a4bb8da3d36922cb5362977af9b3052624525def05e5236f4f922384c31
size 54297
oid sha256:ecee6cd60257a931f717c8ae23f256ded8dd6ac8fa7af7430082c6a3f837b274
size 52821

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:824774a1e19974090de4cb32e06a7f984b5db238c3dc8d04a35d3457e647ac84
size 31999
oid sha256:8449c20314175d6e829df65b2b1ba3cf36944a679b2e360ee5d2ed4d8333ad07
size 32149

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0820b462a0dc103b66aef78528ae5ce13a55e2614a7a197e06713366c422893
size 54313
oid sha256:5c38a6f66d48174c6e2b3ac8525906cd371030f15ac3ca8727a8ca83f528916e
size 52823