Show new notification sound banner logic
This commit is contained in:
committed by
Benoit Marty
parent
71d2c1d9df
commit
4475ed0d37
@@ -251,6 +251,12 @@ private fun RoomsViewList(
|
||||
item {
|
||||
BatteryOptimizationBanner(state = state.batteryOptimizationState)
|
||||
}
|
||||
} else if (state.showNewNotificationSoundBanner) {
|
||||
item {
|
||||
NewNotificationSoundBanner(
|
||||
onDismissClick = { updatedEventSink(RoomListEvents.DismissNewNotificationSoundBanner) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,17 +26,22 @@ open class RoomListContentStateProvider : PreviewParameterProvider<RoomListConte
|
||||
aSkeletonContentState(),
|
||||
anEmptyContentState(),
|
||||
anEmptyContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
||||
aRoomsContentState(
|
||||
showNewNotificationSoundBanner = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aRoomsContentState(
|
||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||
showNewNotificationSoundBanner: Boolean = false,
|
||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||
batteryOptimizationState: BatteryOptimizationState = aBatteryOptimizationState(),
|
||||
seenRoomInvites: Set<RoomId> = emptySet(),
|
||||
) = RoomListContentState.Rooms(
|
||||
securityBannerState = securityBannerState,
|
||||
showNewNotificationSoundBanner = showNewNotificationSoundBanner,
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
||||
batteryOptimizationState = batteryOptimizationState,
|
||||
summaries = summaries,
|
||||
|
||||
@@ -14,6 +14,7 @@ sealed interface RoomListEvents {
|
||||
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
|
||||
data object DismissRequestVerificationPrompt : RoomListEvents
|
||||
data object DismissBanner : RoomListEvents
|
||||
data object DismissNewNotificationSoundBanner : RoomListEvents
|
||||
data object ToggleSearchResults : RoomListEvents
|
||||
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ class RoomListPresenter(
|
||||
}
|
||||
|
||||
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
||||
val showNewNotificationSoundBanner by appPreferencesStore.showNewNotificationSoundBanner().collectAsState(false)
|
||||
|
||||
// Avatar indicator
|
||||
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
||||
@@ -112,6 +113,9 @@ class RoomListPresenter(
|
||||
}
|
||||
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
||||
RoomListEvents.DismissBanner -> securityBannerDismissed = true
|
||||
RoomListEvents.DismissNewNotificationSoundBanner -> coroutineScope.launch {
|
||||
appPreferencesStore.setShowNewNotificationSoundBanner(false)
|
||||
}
|
||||
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||
is RoomListEvents.ShowContextMenu -> {
|
||||
coroutineScope.showContextMenu(event, contextMenu)
|
||||
@@ -141,7 +145,10 @@ class RoomListPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
val contentState = roomListContentState(securityBannerDismissed)
|
||||
val contentState = roomListContentState(
|
||||
securityBannerDismissed,
|
||||
showNewNotificationSoundBanner,
|
||||
)
|
||||
|
||||
val canReportRoom by produceState(false) { value = client.canReportRoom() }
|
||||
|
||||
@@ -197,6 +204,7 @@ class RoomListPresenter(
|
||||
@Composable
|
||||
private fun roomListContentState(
|
||||
securityBannerDismissed: Boolean,
|
||||
showNewNotificationSoundBanner: Boolean,
|
||||
): RoomListContentState {
|
||||
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
|
||||
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
|
||||
@@ -215,11 +223,14 @@ class RoomListPresenter(
|
||||
val seenRoomInvites by remember { seenInvitesStore.seenRoomIds() }.collectAsState(emptySet())
|
||||
val securityBannerState by rememberSecurityBannerState(securityBannerDismissed)
|
||||
return when {
|
||||
showEmpty -> RoomListContentState.Empty(securityBannerState = securityBannerState)
|
||||
showEmpty -> RoomListContentState.Empty(
|
||||
securityBannerState = securityBannerState,
|
||||
)
|
||||
showSkeleton -> RoomListContentState.Skeleton(count = 16)
|
||||
else -> {
|
||||
RoomListContentState.Rooms(
|
||||
securityBannerState = securityBannerState,
|
||||
showNewNotificationSoundBanner = showNewNotificationSoundBanner,
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
||||
batteryOptimizationState = batteryOptimizationPresenter.present(),
|
||||
summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList(),
|
||||
|
||||
@@ -69,6 +69,7 @@ sealed interface RoomListContentState {
|
||||
val securityBannerState: SecurityBannerState,
|
||||
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
||||
val batteryOptimizationState: BatteryOptimizationState,
|
||||
val showNewNotificationSoundBanner: Boolean,
|
||||
val summaries: ImmutableList<RoomListRoomSummary>,
|
||||
val seenRoomInvites: ImmutableSet<RoomId>,
|
||||
) : RoomListContentState
|
||||
|
||||
@@ -75,6 +75,7 @@ import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -593,6 +594,38 @@ class RoomListPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - notification sound banner`() = runTest {
|
||||
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val roomSummary = aRoomSummary(
|
||||
currentUserMembership = CurrentUserMembership.INVITED
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val store = InMemoryAppPreferencesStore()
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
appPreferencesStore = store,
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||
skipItems(1)
|
||||
val state = awaitItem()
|
||||
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||
store.setShowNewNotificationSoundBanner(true)
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isTrue()
|
||||
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue()
|
||||
state.eventSink(RoomListEvents.DismissNewNotificationSoundBanner)
|
||||
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||
// Ensure store has been updated
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createRoomListPresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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.migration.impl.migrations
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesIntoSet
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
|
||||
/**
|
||||
* Ensure the new notification sound banner is displayed, but only on application upgrade.
|
||||
*/
|
||||
@ContributesIntoSet(AppScope::class)
|
||||
@Inject
|
||||
class AppMigration08(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : AppMigration {
|
||||
override val order: Int = 8
|
||||
|
||||
override suspend fun migrate(isFreshInstall: Boolean) {
|
||||
if (!isFreshInstall) {
|
||||
appPreferencesStore.setShowNewNotificationSoundBanner(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector 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.migration.impl.migrations
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class AppMigration08Test {
|
||||
@Test
|
||||
fun `migration on fresh install should not modify the store`() = runTest {
|
||||
val store = InMemoryAppPreferencesStore()
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||
val migration = AppMigration08(store)
|
||||
migration.migrate(isFreshInstall = true)
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migration on upgrade should modify the store`() = runTest {
|
||||
val store = InMemoryAppPreferencesStore()
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
||||
val migration = AppMigration08(store)
|
||||
migration.migrate(isFreshInstall = false)
|
||||
assertThat(store.showNewNotificationSoundBanner().first()).isTrue()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user