mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Allow displaying only unread notifications (#7803)
* Allow displaying only unread notifications Add `FeatureFlags.ShowAllActivityInRoomList` (enabled by default, to match the current behavior) that, when disabled, will display the unread messages of a room instead of unread notifications if no unread notifications are found * Restore UI changes to the badges, `RoomSummaryRow` and `HorizontalFloatingToolbar` * Fix `NavigationBarIcon`: use `BadgeBox` component. * Adjust padding in `RoomSummaryRow` to 6.dp * Fix other indicators not displaying a dot anymore * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
a3ff7e1486
commit
2dd0a47eb4
+1
@@ -280,6 +280,7 @@ private fun RoomsViewList(
|
||||
isInviteSeen = room.displayType == RoomSummaryDisplayType.INVITE &&
|
||||
state.seenRoomInvites.contains(room.roomId),
|
||||
showUnreadCount = state.showUnreadCount,
|
||||
showAllActivity = state.showAllActivity,
|
||||
onClick = onRoomClick,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
+35
-14
@@ -80,6 +80,7 @@ internal fun RoomSummaryRow(
|
||||
onClick: (RoomListRoomSummary) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
showUnreadCount: Boolean = false,
|
||||
showAllActivity: Boolean = false,
|
||||
eventSink: (RoomListEvent) -> Unit,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
@@ -131,7 +132,7 @@ internal fun RoomSummaryRow(
|
||||
isHighlighted = room.isHighlighted,
|
||||
dmUserStatus = room.dmUserStatus,
|
||||
)
|
||||
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
|
||||
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount, showAllActivity = showAllActivity)
|
||||
}
|
||||
}
|
||||
RoomSummaryDisplayType.KNOCKED -> {
|
||||
@@ -278,6 +279,7 @@ private fun InviteSubtitle(
|
||||
private fun MessagePreviewAndIndicatorRow(
|
||||
room: RoomListRoomSummary,
|
||||
showUnreadCount: Boolean,
|
||||
showAllActivity: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
@@ -348,7 +350,7 @@ private fun MessagePreviewAndIndicatorRow(
|
||||
.height(16.dp)
|
||||
// Used to force this line to be read aloud earlier than the latest event when using Talkback
|
||||
.zIndex(-1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val tint = if (room.isHighlighted) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
|
||||
@@ -358,25 +360,17 @@ private fun MessagePreviewAndIndicatorRow(
|
||||
isAudio = room.activeCallIntent == CallIntent.AUDIO
|
||||
)
|
||||
}
|
||||
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
||||
val isMuted = room.userDefinedNotificationMode == RoomNotificationMode.MUTE
|
||||
if (isMuted) {
|
||||
NotificationOffIndicatorAtom()
|
||||
} else if (room.numberOfUnreadMentions > 0) {
|
||||
MentionIndicatorAtom()
|
||||
}
|
||||
if (room.hasNewContent) {
|
||||
val contentDescription = stringResource(CommonStrings.a11y_notifications_new_messages)
|
||||
val count = if (showUnreadCount) {
|
||||
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
||||
room.numberOfUnreadMessages
|
||||
} else {
|
||||
room.numberOfUnreadNotifications
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
UnreadIndicatorAtom(
|
||||
color = tint,
|
||||
count = count,
|
||||
count = room.unreadIndicatorCount(showUnreadCount = showUnreadCount, showAllActivity = showAllActivity),
|
||||
contentDescription = contentDescription,
|
||||
)
|
||||
}
|
||||
@@ -384,6 +378,30 @@ private fun MessagePreviewAndIndicatorRow(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the value to pass to [UnreadIndicatorAtom] as `count`.
|
||||
* - `null`: nothing is displayed.
|
||||
* - `0`: dot indicator without a count.
|
||||
* - `> 0`: the number to display.
|
||||
*/
|
||||
internal fun RoomListRoomSummary.unreadIndicatorCount(
|
||||
showUnreadCount: Boolean,
|
||||
showAllActivity: Boolean,
|
||||
): Long? {
|
||||
val isMuted = userDefinedNotificationMode == RoomNotificationMode.MUTE
|
||||
return when {
|
||||
// Show only unread notifications when the room is not muted and there are unread notifications
|
||||
!isMuted && numberOfUnreadNotifications > 0 -> numberOfUnreadNotifications
|
||||
// Otherwise, show unread messages when showAllActivity is true
|
||||
showAllActivity -> numberOfUnreadMessages.takeIf { it > 0 }
|
||||
// Or don't display anything
|
||||
else -> null
|
||||
}?.let {
|
||||
// We only want to show the count if the option is enabled, otherwise we just show the dot indicator (value 0)
|
||||
if (showUnreadCount) it else 0
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InviteNameAndIndicatorRow(
|
||||
name: String?,
|
||||
@@ -408,7 +426,8 @@ private fun InviteNameAndIndicatorRow(
|
||||
)
|
||||
if (!isInviteSeen) {
|
||||
UnreadIndicatorAtom(
|
||||
color = ElementTheme.colors.unreadIndicator
|
||||
color = ElementTheme.colors.unreadIndicator,
|
||||
count = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -457,5 +476,7 @@ internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryPreviewP
|
||||
isInviteSeen = data.name == "Bob",
|
||||
onClick = {},
|
||||
eventSink = {},
|
||||
showUnreadCount = data.numberOfUnreadNotifications > 0,
|
||||
showAllActivity = true,
|
||||
)
|
||||
}
|
||||
|
||||
+18
@@ -82,6 +82,24 @@ open class RoomListRoomSummaryPreviewParam : PreviewParameterProvider<RoomListRo
|
||||
numberOfUnreadMentions = 1,
|
||||
hasRoomCall = hasCall,
|
||||
),
|
||||
aRoomListRoomSummary(
|
||||
name = roomNotificationMode.name,
|
||||
latestEvent = LatestEvent.Synced("New notifications" + if (hasCall) ", call" else ""),
|
||||
notificationMode = roomNotificationMode,
|
||||
numberOfUnreadMessages = 0,
|
||||
numberOfUnreadMentions = 0,
|
||||
numberOfUnreadNotifications = 1,
|
||||
hasRoomCall = hasCall,
|
||||
),
|
||||
aRoomListRoomSummary(
|
||||
name = roomNotificationMode.name,
|
||||
latestEvent = LatestEvent.Synced("New notifications" + if (hasCall) ", call" else ""),
|
||||
notificationMode = roomNotificationMode,
|
||||
numberOfUnreadMessages = 0,
|
||||
numberOfUnreadMentions = 0,
|
||||
numberOfUnreadNotifications = 100,
|
||||
hasRoomCall = hasCall,
|
||||
),
|
||||
)
|
||||
}.flatten()
|
||||
}.flatten(),
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ internal fun aRoomsContentState(
|
||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||
batteryOptimizationState: BatteryOptimizationState = aBatteryOptimizationState(),
|
||||
showAllActivity: Boolean = false,
|
||||
seenRoomInvites: Set<RoomId> = emptySet(),
|
||||
) = RoomListContentState.Rooms(
|
||||
securityBannerState = securityBannerState,
|
||||
@@ -48,6 +49,7 @@ internal fun aRoomsContentState(
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
||||
batteryOptimizationState = batteryOptimizationState,
|
||||
summaries = summaries,
|
||||
showAllActivity = showAllActivity,
|
||||
seenRoomInvites = seenRoomInvites.toImmutableSet(),
|
||||
)
|
||||
|
||||
|
||||
+8
-1
@@ -118,6 +118,10 @@ class RoomListPresenter(
|
||||
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
||||
val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) }
|
||||
|
||||
val showAllActivity by produceState(initialValue = false) {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.ShowAllActivityInRoomList).collect { value = it }
|
||||
}
|
||||
|
||||
fun handleEvent(event: RoomListEvent) {
|
||||
when (event) {
|
||||
is RoomListEvent.UpdateVisibleRange -> coroutineScope.launch {
|
||||
@@ -165,13 +169,14 @@ class RoomListPresenter(
|
||||
|
||||
val canReportRoom by produceState(false) { value = client.canReportRoom() }
|
||||
val showUnreadCount by produceState(false) {
|
||||
value = featureFlagService.isFeatureEnabled(FeatureFlags.UnreadIndicatorCount)
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.UnreadIndicatorCount).collect { value = it }
|
||||
}
|
||||
|
||||
val contentState = roomListContentState(
|
||||
securityBannerDismissed,
|
||||
showNewNotificationSoundBanner,
|
||||
showUnreadCount,
|
||||
showAllActivity,
|
||||
)
|
||||
|
||||
return RoomListState(
|
||||
@@ -230,6 +235,7 @@ class RoomListPresenter(
|
||||
securityBannerDismissed: Boolean,
|
||||
showNewNotificationSoundBanner: Boolean,
|
||||
showUnreadCount: Boolean,
|
||||
showAllActivity: Boolean,
|
||||
): RoomListContentState {
|
||||
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
|
||||
roomListDataSource.roomSummariesFlow.collect { value = AsyncData.Success(it) }
|
||||
@@ -262,6 +268,7 @@ class RoomListPresenter(
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
||||
batteryOptimizationState = batteryOptimizationPresenter.present(),
|
||||
summaries = roomSummaries.dataOrNull().orEmpty().toImmutableList(),
|
||||
showAllActivity = showAllActivity,
|
||||
seenRoomInvites = seenRoomInvites.toImmutableSet(),
|
||||
)
|
||||
}
|
||||
|
||||
+1
@@ -76,5 +76,6 @@ sealed interface RoomListContentState {
|
||||
val showUnreadCount: Boolean,
|
||||
val summaries: ImmutableList<RoomListRoomSummary>,
|
||||
val seenRoomInvites: ImmutableSet<RoomId>,
|
||||
val showAllActivity: Boolean,
|
||||
) : RoomListContentState
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.home.impl.model.createRoomListRoomSummary
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import org.junit.Test
|
||||
|
||||
class RoomSummaryRowUnreadIndicatorCountTest {
|
||||
@Test
|
||||
fun `unread notifications are used when the room is not muted`() {
|
||||
val room = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 3,
|
||||
numberOfUnreadMessages = 5,
|
||||
)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = false)).isEqualTo(3L)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = true)).isEqualTo(3L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unread notifications only show a dot when showUnreadCount is false`() {
|
||||
val room = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 3,
|
||||
numberOfUnreadMessages = 5,
|
||||
)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = false, showAllActivity = false)).isEqualTo(0L)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = false, showAllActivity = true)).isEqualTo(0L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unread messages are used when there are no unread notifications and showAllActivity is true`() {
|
||||
val room = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 0,
|
||||
numberOfUnreadMessages = 5,
|
||||
)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = true)).isEqualTo(5L)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = false, showAllActivity = true)).isEqualTo(0L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unread messages are ignored when showAllActivity is false`() {
|
||||
val room = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 0,
|
||||
numberOfUnreadMessages = 5,
|
||||
)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = false)).isNull()
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = false, showAllActivity = false)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `muted room ignores unread notifications and falls back to unread messages when showAllActivity is true`() {
|
||||
val room = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 3,
|
||||
numberOfUnreadMessages = 5,
|
||||
userDefinedNotificationMode = RoomNotificationMode.MUTE,
|
||||
)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = true)).isEqualTo(5L)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = false, showAllActivity = true)).isEqualTo(0L)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = false)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no count when there are no unread notifications nor unread messages`() {
|
||||
val room = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 0,
|
||||
numberOfUnreadMessages = 0,
|
||||
isMarkedUnread = true,
|
||||
)
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = true, showAllActivity = true)).isNull()
|
||||
assertThat(room.unreadIndicatorCount(showUnreadCount = false, showAllActivity = false)).isNull()
|
||||
}
|
||||
}
|
||||
+50
-4
@@ -39,6 +39,7 @@ import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
||||
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
|
||||
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.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -218,7 +219,7 @@ class RoomListPresenterTest {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Skip intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
// Skip intermediate event for loading the value of the feature flags
|
||||
skipItems(1)
|
||||
|
||||
val summary = createRoomListRoomSummary()
|
||||
@@ -265,7 +266,7 @@ class RoomListPresenterTest {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Skip intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
// Skip intermediate event for loading the value of the feature flags
|
||||
skipItems(1)
|
||||
|
||||
val summary = createRoomListRoomSummary()
|
||||
@@ -318,7 +319,7 @@ class RoomListPresenterTest {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Skip intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
// Skip intermediate event for loading the value of the feature flags
|
||||
skipItems(1)
|
||||
|
||||
eventRecorder.assertEmpty()
|
||||
@@ -629,7 +630,7 @@ class RoomListPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(announcementService.announcementsToShowFlow().first()).isEmpty()
|
||||
skipItems(2) // Skip initial state and intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
skipItems(2) // Skip initial state and intermediate event for loading the value of the feature flags
|
||||
val state = awaitItem()
|
||||
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||
announcementService.emitAnnouncementsToShow(listOf(Announcement.NewNotificationSound))
|
||||
@@ -643,6 +644,51 @@ class RoomListPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - showAllActivity and showUnreadCount reflect the feature flag values`() = runTest {
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(aRoomSummary())),
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1)),
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = FakeRoomListService(createRoomListLambda = { roomList }),
|
||||
)
|
||||
val featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(
|
||||
FeatureFlags.ShowAllActivityInRoomList.key to true,
|
||||
FeatureFlags.UnreadIndicatorCount.key to true,
|
||||
)
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
presenter.test {
|
||||
val enabledState = consumeItemsUntilPredicate { state ->
|
||||
state.contentState is RoomListContentState.Rooms &&
|
||||
state.contentAsRooms().showAllActivity &&
|
||||
state.contentAsRooms().showUnreadCount
|
||||
}.last()
|
||||
assertThat(enabledState.contentAsRooms().showAllActivity).isTrue()
|
||||
assertThat(enabledState.contentAsRooms().showUnreadCount).isTrue()
|
||||
|
||||
featureFlagService.setFeatureEnabled(FeatureFlags.ShowAllActivityInRoomList, false)
|
||||
val allActivityDisabledState = consumeItemsUntilPredicate { state ->
|
||||
state.contentState is RoomListContentState.Rooms && !state.contentAsRooms().showAllActivity
|
||||
}.last()
|
||||
assertThat(allActivityDisabledState.contentAsRooms().showAllActivity).isFalse()
|
||||
assertThat(allActivityDisabledState.contentAsRooms().showUnreadCount).isTrue()
|
||||
|
||||
featureFlagService.setFeatureEnabled(FeatureFlags.UnreadIndicatorCount, false)
|
||||
val unreadCountDisabledState = consumeItemsUntilPredicate { state ->
|
||||
state.contentState is RoomListContentState.Rooms && !state.contentAsRooms().showUnreadCount
|
||||
}.last()
|
||||
assertThat(unreadCountDisabledState.contentAsRooms().showAllActivity).isFalse()
|
||||
assertThat(unreadCountDisabledState.contentAsRooms().showUnreadCount).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTestMarkRoomAsRead(
|
||||
client: MatrixClient,
|
||||
notificationCleaner: NotificationCleaner,
|
||||
|
||||
+1
@@ -267,6 +267,7 @@ private fun ThreadListItemRow(
|
||||
UnreadIndicatorAtom(
|
||||
size = 14.dp,
|
||||
isVisible = hasUnreadNotifications,
|
||||
count = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -580,6 +580,7 @@ private fun JumpToPositionButton(
|
||||
.offset { IntOffset(x = 0, y = dotYOffset.roundToPx()) },
|
||||
color = ElementTheme.colors.iconSuccessPrimary,
|
||||
border = BorderStroke(2.dp, ElementTheme.colors.bgCanvasDefault),
|
||||
count = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -34,4 +34,5 @@ sealed interface NotificationSettingsEvent {
|
||||
data object DismissCallRingtoneDialog : NotificationSettingsEvent
|
||||
data class SelectCallRingtonePreset(val sound: NotificationSound) : NotificationSettingsEvent
|
||||
data object LaunchCallRingtonePicker : NotificationSettingsEvent
|
||||
data object ToggleShowAllActivityInRoomList : NotificationSettingsEvent
|
||||
}
|
||||
|
||||
+12
@@ -28,6 +28,8 @@ import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
@@ -73,6 +75,7 @@ class NotificationSettingsPresenter(
|
||||
private val stringProvider: StringProvider,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : Presenter<NotificationSettingsState> {
|
||||
// Serializes the pick → copy → persist → recreate pipeline per slot. The copier already locks
|
||||
// its `<slot>.tmp` file, but the persist+recreate window outside the copier is unprotected;
|
||||
@@ -196,6 +199,11 @@ class NotificationSettingsPresenter(
|
||||
)
|
||||
}
|
||||
|
||||
val showAllActivityInRoomList by produceState(false) {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.ShowAllActivityInRoomList)
|
||||
.collect { value = it }
|
||||
}
|
||||
|
||||
fun handleEvent(event: NotificationSettingsEvent) {
|
||||
when (event) {
|
||||
is NotificationSettingsEvent.SetAtRoomNotificationsEnabled -> {
|
||||
@@ -276,6 +284,9 @@ class NotificationSettingsPresenter(
|
||||
NotificationSettingsEvent.DismissCallRingtoneCopyError -> {
|
||||
callRingtoneCopyError = false
|
||||
}
|
||||
NotificationSettingsEvent.ToggleShowAllActivityInRoomList -> localCoroutineScope.launch {
|
||||
featureFlagService.setFeatureEnabled(FeatureFlags.ShowAllActivityInRoomList, !showAllActivityInRoomList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +315,7 @@ class NotificationSettingsPresenter(
|
||||
showCallRingtoneDialog = showCallRingtoneDialog,
|
||||
pendingMessageSoundPickerLaunch = pendingMessageSoundPickerLaunch,
|
||||
pendingCallRingtonePickerLaunch = pendingCallRingtonePickerLaunch,
|
||||
showAllActivityInRoomList = showAllActivityInRoomList,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
+4
@@ -42,6 +42,10 @@ data class NotificationSettingsState(
|
||||
* auto-opens on screen entry.
|
||||
*/
|
||||
val pendingCallRingtonePickerLaunch: Int,
|
||||
/**
|
||||
* Whether to show all activity indicators in the room list, using unread messages instead of notifications.
|
||||
*/
|
||||
val showAllActivityInRoomList: Boolean,
|
||||
val eventSink: (NotificationSettingsEvent) -> Unit,
|
||||
) {
|
||||
sealed interface MatrixSettings {
|
||||
|
||||
+3
@@ -99,6 +99,7 @@ fun aValidNotificationSettingsState(
|
||||
callRingtoneCopyError: Boolean = false,
|
||||
showMessageSoundDialog: Boolean = false,
|
||||
showCallRingtoneDialog: Boolean = false,
|
||||
showAllActivityInRoomList: Boolean = false,
|
||||
pendingMessageSoundPickerLaunch: Int = 0,
|
||||
pendingCallRingtonePickerLaunch: Int = 0,
|
||||
eventSink: (NotificationSettingsEvent) -> Unit = {},
|
||||
@@ -133,6 +134,7 @@ fun aValidNotificationSettingsState(
|
||||
showCallRingtoneDialog = showCallRingtoneDialog,
|
||||
pendingMessageSoundPickerLaunch = pendingMessageSoundPickerLaunch,
|
||||
pendingCallRingtonePickerLaunch = pendingCallRingtonePickerLaunch,
|
||||
showAllActivityInRoomList = showAllActivityInRoomList,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
@@ -166,6 +168,7 @@ fun aInvalidNotificationSettingsState(
|
||||
showCallRingtoneDialog = false,
|
||||
pendingMessageSoundPickerLaunch = 0,
|
||||
pendingCallRingtonePickerLaunch = 0,
|
||||
showAllActivityInRoomList = false,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
||||
+10
@@ -199,6 +199,16 @@ private fun NotificationSettingsContentView(
|
||||
onCheckedChange = onMentionNotificationsChange
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceCategory {
|
||||
PreferenceSwitch(
|
||||
title = stringResource(R.string.screen_notification_settings_show_all_activity_title),
|
||||
subtitle = stringResource(R.string.screen_notification_settings_show_all_activity_subtitle),
|
||||
isChecked = state.showAllActivityInRoomList,
|
||||
onCheckedChange = { state.eventSink(NotificationSettingsEvent.ToggleShowAllActivityInRoomList) }
|
||||
)
|
||||
}
|
||||
|
||||
SoundsPreferenceCategory(state = state)
|
||||
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_additional_settings_section_title)) {
|
||||
// TODO We are removing the call notification toggle until support for call notifications has been added
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
<string name="screen_notification_settings_mode_mentions">"Згадки"</string>
|
||||
<string name="screen_notification_settings_notification_section_title">"Сповіщати мене про"</string>
|
||||
<string name="screen_notification_settings_room_mention_label">"Згадки @room"</string>
|
||||
<string name="screen_notification_settings_show_all_activity">"Показати всю діяльність у списку кімнат (крапки або кількість непрочитаних повідомлень)"</string>
|
||||
<string name="screen_notification_settings_sound_custom_fallback">"Власний"</string>
|
||||
<string name="screen_notification_settings_sound_custom_sound_button_title">"Користувацький звук…"</string>
|
||||
<string name="screen_notification_settings_sound_delete_sound_error_title">"Помилка видалення файлу"</string>
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
<string name="screen_notification_settings_mode_mentions">"提及"</string>
|
||||
<string name="screen_notification_settings_notification_section_title">"通知我以下类型"</string>
|
||||
<string name="screen_notification_settings_room_mention_label">"含有 @room 的提及"</string>
|
||||
<string name="screen_notification_settings_show_all_activity">"在房间列表上显示所有活动(用于未读消息的圆点或数字)"</string>
|
||||
<string name="screen_notification_settings_sound_custom_fallback">"自定义"</string>
|
||||
<string name="screen_notification_settings_sound_custom_sound_button_title">"自定义提示音…"</string>
|
||||
<string name="screen_notification_settings_sound_delete_sound_error_title">"删除文件时出错"</string>
|
||||
|
||||
@@ -92,7 +92,8 @@ If you proceed, some of your settings may change."</string>
|
||||
<string name="screen_notification_settings_mode_mentions">"Mentions"</string>
|
||||
<string name="screen_notification_settings_notification_section_title">"Notify me for"</string>
|
||||
<string name="screen_notification_settings_room_mention_label">"Mentions of @room"</string>
|
||||
<string name="screen_notification_settings_show_all_activity">"Show all activity in the room list (dots or number of unread messages)"</string>
|
||||
<string name="screen_notification_settings_show_all_activity_subtitle">"Dots or number of unread messages"</string>
|
||||
<string name="screen_notification_settings_show_all_activity_title">"Show all activity in the room list"</string>
|
||||
<string name="screen_notification_settings_sound_custom_fallback">"Custom"</string>
|
||||
<string name="screen_notification_settings_sound_custom_sound_button_title">"Custom sound…"</string>
|
||||
<string name="screen_notification_settings_sound_delete_sound_error_title">"Error deleting file"</string>
|
||||
|
||||
+45
@@ -10,6 +10,9 @@ package io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
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.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -1104,6 +1107,46 @@ class NotificationSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - showAllActivityInRoomList reflects the feature flag value`() = runTest {
|
||||
val featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.ShowAllActivityInRoomList.key to true)
|
||||
)
|
||||
val presenter = createNotificationSettingsPresenter(featureFlagService = featureFlagService)
|
||||
presenter.test {
|
||||
val state = consumeItemsUntilPredicate { it.showAllActivityInRoomList }.last()
|
||||
assertThat(state.showAllActivityInRoomList).isTrue()
|
||||
featureFlagService.setFeatureEnabled(FeatureFlags.ShowAllActivityInRoomList, false)
|
||||
|
||||
val updatedState = consumeItemsUntilPredicate { !it.showAllActivityInRoomList }.last()
|
||||
assertThat(updatedState.showAllActivityInRoomList).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - ToggleShowAllActivityInRoomList toggles the feature flag`() = runTest {
|
||||
val featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.ShowAllActivityInRoomList.key to false)
|
||||
)
|
||||
val presenter = createNotificationSettingsPresenter(featureFlagService = featureFlagService)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showAllActivityInRoomList).isFalse()
|
||||
initialState.eventSink(NotificationSettingsEvent.ToggleShowAllActivityInRoomList)
|
||||
|
||||
val enabledState = consumeItemsUntilPredicate { it.showAllActivityInRoomList }.last()
|
||||
assertThat(enabledState.showAllActivityInRoomList).isTrue()
|
||||
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowAllActivityInRoomList)).isTrue()
|
||||
enabledState.eventSink(NotificationSettingsEvent.ToggleShowAllActivityInRoomList)
|
||||
|
||||
val disabledState = consumeItemsUntilPredicate { !it.showAllActivityInRoomList }.last()
|
||||
assertThat(disabledState.showAllActivityInRoomList).isFalse()
|
||||
assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowAllActivityInRoomList)).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createNotificationSettingsPresenter(
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
pushService: PushService = FakePushService(),
|
||||
@@ -1116,6 +1159,7 @@ class NotificationSettingsPresenterTest {
|
||||
NotificationSoundCopier.CopyResult.Success(source, source)
|
||||
}),
|
||||
soundDisplayNameResolver: FakeSoundDisplayNameResolver = FakeSoundDisplayNameResolver(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
): NotificationSettingsPresenter {
|
||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||
return NotificationSettingsPresenter(
|
||||
@@ -1131,6 +1175,7 @@ class NotificationSettingsPresenterTest {
|
||||
soundDisplayNameResolver = soundDisplayNameResolver,
|
||||
stringProvider = FakeStringProvider(),
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -183,6 +183,35 @@ class NotificationSettingsViewTest : RobolectricTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on disable show all activity emits the expected events`() {
|
||||
testShowAllActivityToggle(true)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on enable show all activity emits the expected events`() {
|
||||
testShowAllActivityToggle(false)
|
||||
}
|
||||
|
||||
private fun testShowAllActivityToggle(initialState: Boolean) = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvent>()
|
||||
setNotificationSettingsView(
|
||||
state = aValidNotificationSettingsState(
|
||||
showAllActivityInRoomList = initialState,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
clickOn(R.string.screen_notification_settings_show_all_activity_title)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
NotificationSettingsEvent.RefreshSystemNotificationsEnabled,
|
||||
NotificationSettingsEvent.ToggleShowAllActivityInRoomList,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `with an error configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest {
|
||||
|
||||
Reference in New Issue
Block a user