Merge pull request #5482 from element-hq/feature/bma/improveAnnouncementService
Improve AnnouncementService.
This commit is contained in:
commit
29367d094c
21 changed files with 223 additions and 86 deletions
|
|
@ -9,4 +9,5 @@ package io.element.android.features.announcement.api
|
||||||
|
|
||||||
enum class Announcement {
|
enum class Announcement {
|
||||||
Space,
|
Space,
|
||||||
|
NewNotificationSound,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,18 @@ package io.element.android.features.announcement.api
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AnnouncementService {
|
interface AnnouncementService {
|
||||||
suspend fun showAnnouncement(announcement: Announcement)
|
suspend fun showAnnouncement(announcement: Announcement)
|
||||||
|
|
||||||
|
suspend fun onAnnouncementDismissed(announcement: Announcement)
|
||||||
|
|
||||||
|
fun announcementsToShowFlow(): Flow<List<Announcement>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this composable to render the announcement UI in Fullscreen.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun Render(
|
fun Render(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
@ -23,8 +25,8 @@ class AnnouncementPresenter(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): AnnouncementState {
|
override fun present(): AnnouncementState {
|
||||||
val showSpaceAnnouncement by remember {
|
val showSpaceAnnouncement by remember {
|
||||||
announcementStore.spaceAnnouncementFlow().map {
|
announcementStore.announcementStatusFlow(Announcement.Space).map {
|
||||||
it == AnnouncementStore.SpaceAnnouncement.Show
|
it == AnnouncementStatus.Show
|
||||||
}
|
}
|
||||||
}.collectAsState(false)
|
}.collectAsState(false)
|
||||||
return AnnouncementState(
|
return AnnouncementState(
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,11 @@ import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.api.AnnouncementService
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementView
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementView
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
|
|
@ -35,13 +38,36 @@ class DefaultAnnouncementService(
|
||||||
override suspend fun showAnnouncement(announcement: Announcement) {
|
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||||
when (announcement) {
|
when (announcement) {
|
||||||
Announcement.Space -> showSpaceAnnouncement()
|
Announcement.Space -> showSpaceAnnouncement()
|
||||||
|
Announcement.NewNotificationSound -> {
|
||||||
|
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onAnnouncementDismissed(announcement: Announcement) {
|
||||||
|
announcementStore.setAnnouncementStatus(announcement, AnnouncementStatus.Shown)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun announcementsToShowFlow(): Flow<List<Announcement>> {
|
||||||
|
return combine(
|
||||||
|
announcementStore.announcementStatusFlow(Announcement.Space),
|
||||||
|
announcementStore.announcementStatusFlow(Announcement.NewNotificationSound),
|
||||||
|
) { spaceAnnouncementStatus, newNotificationSoundStatus ->
|
||||||
|
buildList {
|
||||||
|
if (spaceAnnouncementStatus == AnnouncementStatus.Show) {
|
||||||
|
add(Announcement.Space)
|
||||||
|
}
|
||||||
|
if (newNotificationSoundStatus == AnnouncementStatus.Show) {
|
||||||
|
add(Announcement.NewNotificationSound)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun showSpaceAnnouncement() {
|
private suspend fun showSpaceAnnouncement() {
|
||||||
val currentValue = announcementStore.spaceAnnouncementFlow().first()
|
val currentValue = announcementStore.announcementStatusFlow(Announcement.Space).first()
|
||||||
if (currentValue == AnnouncementStore.SpaceAnnouncement.NeverShown) {
|
if (currentValue == AnnouncementStatus.NeverShown) {
|
||||||
announcementStore.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Show)
|
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ package io.element.android.features.announcement.impl.spaces
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore.SpaceAnnouncement
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ class SpaceAnnouncementPresenter(
|
||||||
fun handleEvents(event: SpaceAnnouncementEvents) {
|
fun handleEvents(event: SpaceAnnouncementEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch {
|
SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch {
|
||||||
announcementStore.setSpaceAnnouncementValue(SpaceAnnouncement.Shown)
|
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* 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.announcement.impl.store
|
||||||
|
|
||||||
|
enum class AnnouncementStatus {
|
||||||
|
NeverShown,
|
||||||
|
Show,
|
||||||
|
Shown,
|
||||||
|
}
|
||||||
|
|
@ -7,17 +7,18 @@
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.store
|
package io.element.android.features.announcement.impl.store
|
||||||
|
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AnnouncementStore {
|
interface AnnouncementStore {
|
||||||
suspend fun setSpaceAnnouncementValue(value: SpaceAnnouncement)
|
suspend fun setAnnouncementStatus(
|
||||||
fun spaceAnnouncementFlow(): Flow<SpaceAnnouncement>
|
announcement: Announcement,
|
||||||
|
status: AnnouncementStatus,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun announcementStatusFlow(
|
||||||
|
announcement: Announcement,
|
||||||
|
): Flow<AnnouncementStatus>
|
||||||
|
|
||||||
suspend fun reset()
|
suspend fun reset()
|
||||||
|
|
||||||
enum class SpaceAnnouncement {
|
|
||||||
NeverShown,
|
|
||||||
Show,
|
|
||||||
Shown,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@ import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement")
|
private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement")
|
||||||
|
private val newNotificationSoundKey = intPreferencesKey("newNotificationSound")
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -25,16 +27,23 @@ class DefaultAnnouncementStore(
|
||||||
) : AnnouncementStore {
|
) : AnnouncementStore {
|
||||||
private val store = preferenceDataStoreFactory.create("elementx_announcement")
|
private val store = preferenceDataStoreFactory.create("elementx_announcement")
|
||||||
|
|
||||||
override suspend fun setSpaceAnnouncementValue(value: AnnouncementStore.SpaceAnnouncement) {
|
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
||||||
store.edit {
|
val key = announcement.toKey()
|
||||||
it[spaceAnnouncementKey] = value.ordinal
|
store.edit { prefs ->
|
||||||
|
prefs[key] = status.ordinal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun spaceAnnouncementFlow(): Flow<AnnouncementStore.SpaceAnnouncement> {
|
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
||||||
|
val key = announcement.toKey()
|
||||||
|
// For NewNotificationSound, a migration will set it to Show on application upgrade (see AppMigration08)
|
||||||
|
val defaultStatus = when (announcement) {
|
||||||
|
Announcement.Space -> AnnouncementStatus.NeverShown
|
||||||
|
Announcement.NewNotificationSound -> AnnouncementStatus.Shown
|
||||||
|
}
|
||||||
return store.data.map { prefs ->
|
return store.data.map { prefs ->
|
||||||
val ordinal = prefs[spaceAnnouncementKey] ?: AnnouncementStore.SpaceAnnouncement.NeverShown.ordinal
|
val ordinal = prefs[key] ?: defaultStatus.ordinal
|
||||||
AnnouncementStore.SpaceAnnouncement.entries.getOrElse(ordinal) { AnnouncementStore.SpaceAnnouncement.NeverShown }
|
AnnouncementStatus.entries.getOrElse(ordinal) { defaultStatus }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,3 +51,8 @@ class DefaultAnnouncementStore(
|
||||||
store.edit { it.clear() }
|
store.edit { it.clear() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Announcement.toKey() = when (this) {
|
||||||
|
Announcement.Space -> spaceAnnouncementKey
|
||||||
|
Announcement.NewNotificationSound -> newNotificationSoundKey
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
package io.element.android.features.announcement.impl
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
|
@ -33,10 +35,10 @@ class AnnouncementPresenterTest {
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.showSpaceAnnouncement).isFalse()
|
assertThat(state.showSpaceAnnouncement).isFalse()
|
||||||
store.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Show)
|
store.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
||||||
val updatedState = awaitItem()
|
val updatedState = awaitItem()
|
||||||
assertThat(updatedState.showSpaceAnnouncement).isTrue()
|
assertThat(updatedState.showSpaceAnnouncement).isTrue()
|
||||||
store.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Shown)
|
store.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.showSpaceAnnouncement).isFalse()
|
assertThat(finalState.showSpaceAnnouncement).isFalse()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl
|
package io.element.android.features.announcement.impl
|
||||||
|
|
||||||
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||||
import io.element.android.features.announcement.impl.spaces.aSpaceAnnouncementState
|
import io.element.android.features.announcement.impl.spaces.aSpaceAnnouncementState
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
@ -25,14 +27,49 @@ class DefaultAnnouncementServiceTest {
|
||||||
val sut = createDefaultAnnouncementService(
|
val sut = createDefaultAnnouncementService(
|
||||||
announcementStore = announcementStore,
|
announcementStore = announcementStore,
|
||||||
)
|
)
|
||||||
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.NeverShown)
|
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||||
sut.showAnnouncement(Announcement.Space)
|
sut.showAnnouncement(Announcement.Space)
|
||||||
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Show)
|
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Show)
|
||||||
// Simulate user close the announcement
|
// Simulate user close the announcement
|
||||||
announcementStore.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Shown)
|
sut.onAnnouncementDismissed(Announcement.Space)
|
||||||
// Entering again the space tab should not change the value
|
// Entering again the space tab should not change the value
|
||||||
sut.showAnnouncement(Announcement.Space)
|
sut.showAnnouncement(Announcement.Space)
|
||||||
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Shown)
|
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when showing NewNotificationSound announcement, announcement is set to show even if it was already shown`() = runTest {
|
||||||
|
val announcementStore = InMemoryAnnouncementStore()
|
||||||
|
val sut = createDefaultAnnouncementService(
|
||||||
|
announcementStore = announcementStore,
|
||||||
|
)
|
||||||
|
assertThat(announcementStore.announcementStatusFlow(Announcement.NewNotificationSound).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||||
|
sut.showAnnouncement(Announcement.NewNotificationSound)
|
||||||
|
assertThat(announcementStore.announcementStatusFlow(Announcement.NewNotificationSound).first()).isEqualTo(AnnouncementStatus.Show)
|
||||||
|
// Simulate user close the announcement
|
||||||
|
sut.onAnnouncementDismissed(Announcement.NewNotificationSound)
|
||||||
|
// Calling again showAnnouncement should set it back to Show
|
||||||
|
sut.showAnnouncement(Announcement.NewNotificationSound)
|
||||||
|
assertThat(announcementStore.announcementStatusFlow(Announcement.NewNotificationSound).first()).isEqualTo(AnnouncementStatus.Show)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test announcementsToShowFlow`() = runTest {
|
||||||
|
val announcementStore = InMemoryAnnouncementStore()
|
||||||
|
val sut = createDefaultAnnouncementService(
|
||||||
|
announcementStore = announcementStore,
|
||||||
|
)
|
||||||
|
sut.announcementsToShowFlow().test {
|
||||||
|
assertThat(awaitItem()).isEmpty()
|
||||||
|
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
||||||
|
assertThat(awaitItem()).containsExactly(Announcement.Space)
|
||||||
|
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||||
|
assertThat(awaitItem()).containsExactly(Announcement.Space, Announcement.NewNotificationSound)
|
||||||
|
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
||||||
|
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound)
|
||||||
|
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Shown)
|
||||||
|
assertThat(awaitItem()).isEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDefaultAnnouncementService(
|
private fun createDefaultAnnouncementService(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
package io.element.android.features.announcement.impl.spaces
|
package io.element.android.features.announcement.impl.spaces
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
|
@ -23,10 +25,10 @@ class SpaceAnnouncementPresenterTest {
|
||||||
announcementStore = store,
|
announcementStore = store,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
assertThat(store.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.NeverShown)
|
assertThat(store.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
state.eventSink(SpaceAnnouncementEvents.Continue)
|
state.eventSink(SpaceAnnouncementEvents.Continue)
|
||||||
assertThat(store.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Shown)
|
assertThat(store.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,33 @@
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.store
|
package io.element.android.features.announcement.impl.store
|
||||||
|
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
class InMemoryAnnouncementStore(
|
class InMemoryAnnouncementStore(
|
||||||
initialSpaceAnnouncement: AnnouncementStore.SpaceAnnouncement = AnnouncementStore.SpaceAnnouncement.NeverShown,
|
initialSpaceAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||||
|
initialNewNotificationSoundAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||||
) : AnnouncementStore {
|
) : AnnouncementStore {
|
||||||
private val spaceAnnouncement = MutableStateFlow(initialSpaceAnnouncement)
|
private val spaceAnnouncement = MutableStateFlow(initialSpaceAnnouncementStatus)
|
||||||
override suspend fun setSpaceAnnouncementValue(value: AnnouncementStore.SpaceAnnouncement) {
|
private val newNotificationSoundAnnouncement = MutableStateFlow(initialNewNotificationSoundAnnouncementStatus)
|
||||||
spaceAnnouncement.value = value
|
|
||||||
|
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
||||||
|
announcement.toMutableStateFlow().value = status
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun spaceAnnouncementFlow(): Flow<AnnouncementStore.SpaceAnnouncement> {
|
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
||||||
return spaceAnnouncement.asStateFlow()
|
return announcement.toMutableStateFlow().asStateFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun reset() {
|
override suspend fun reset() {
|
||||||
spaceAnnouncement.value = AnnouncementStore.SpaceAnnouncement.NeverShown
|
spaceAnnouncement.value = AnnouncementStatus.NeverShown
|
||||||
|
newNotificationSoundAnnouncement.value = AnnouncementStatus.NeverShown
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Announcement.toMutableStateFlow() = when (this) {
|
||||||
|
Announcement.Space -> spaceAnnouncement
|
||||||
|
Announcement.NewNotificationSound -> newNotificationSoundAnnouncement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,34 @@ import androidx.compose.ui.Modifier
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.api.AnnouncementService
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
class FakeAnnouncementService(
|
class FakeAnnouncementService(
|
||||||
|
initialAnnouncementsToShowFlowValue: List<Announcement> = emptyList(),
|
||||||
val showAnnouncementResult: (Announcement) -> Unit = { lambdaError() },
|
val showAnnouncementResult: (Announcement) -> Unit = { lambdaError() },
|
||||||
|
val onAnnouncementDismissedResult: (Announcement) -> Unit = { lambdaError() },
|
||||||
val renderResult: (Modifier) -> Unit = { lambdaError() },
|
val renderResult: (Modifier) -> Unit = { lambdaError() },
|
||||||
) : AnnouncementService {
|
) : AnnouncementService {
|
||||||
|
private val announcementsToShowFlowValue = MutableStateFlow(initialAnnouncementsToShowFlowValue)
|
||||||
|
|
||||||
override suspend fun showAnnouncement(announcement: Announcement) {
|
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||||
showAnnouncementResult(announcement)
|
showAnnouncementResult(announcement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun onAnnouncementDismissed(announcement: Announcement) {
|
||||||
|
onAnnouncementDismissedResult(announcement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun announcementsToShowFlow(): Flow<List<Announcement>> {
|
||||||
|
return announcementsToShowFlowValue.asStateFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emitAnnouncementsToShow(value: List<Announcement>) {
|
||||||
|
announcementsToShowFlowValue.value = value
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Render(modifier: Modifier) {
|
override fun Render(modifier: Modifier) {
|
||||||
renderResult(modifier)
|
renderResult(modifier)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
||||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchEvents
|
import io.element.android.features.home.impl.search.RoomListSearchEvents
|
||||||
|
|
@ -82,6 +84,7 @@ class RoomListPresenter(
|
||||||
private val notificationCleaner: NotificationCleaner,
|
private val notificationCleaner: NotificationCleaner,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val seenInvitesStore: SeenInvitesStore,
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
|
private val announcementService: AnnouncementService,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
private val encryptionService = client.encryptionService
|
private val encryptionService = client.encryptionService
|
||||||
|
|
||||||
|
|
@ -98,7 +101,11 @@ class RoomListPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
||||||
val showNewNotificationSoundBanner by appPreferencesStore.showNewNotificationSoundBanner().collectAsState(false)
|
val showNewNotificationSoundBanner by remember {
|
||||||
|
announcementService.announcementsToShowFlow().map { announcements ->
|
||||||
|
announcements.contains(Announcement.NewNotificationSound)
|
||||||
|
}
|
||||||
|
}.collectAsState(false)
|
||||||
|
|
||||||
// Avatar indicator
|
// Avatar indicator
|
||||||
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
|
||||||
|
|
@ -114,7 +121,7 @@ class RoomListPresenter(
|
||||||
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
||||||
RoomListEvents.DismissBanner -> securityBannerDismissed = true
|
RoomListEvents.DismissBanner -> securityBannerDismissed = true
|
||||||
RoomListEvents.DismissNewNotificationSoundBanner -> coroutineScope.launch {
|
RoomListEvents.DismissNewNotificationSoundBanner -> coroutineScope.launch {
|
||||||
appPreferencesStore.setShowNewNotificationSoundBanner(false)
|
announcementService.onAnnouncementDismissed(Announcement.NewNotificationSound)
|
||||||
}
|
}
|
||||||
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
is RoomListEvents.ShowContextMenu -> {
|
is RoomListEvents.ShowContextMenu -> {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
import io.element.android.features.home.impl.FakeDateTimeObserver
|
import io.element.android.features.home.impl.FakeDateTimeObserver
|
||||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
||||||
import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory
|
import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory
|
||||||
|
|
@ -28,6 +30,7 @@ import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInvit
|
||||||
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
|
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
|
|
@ -606,23 +609,27 @@ class RoomListPresenterTest {
|
||||||
)
|
)
|
||||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
roomListService.postAllRooms(listOf(roomSummary))
|
roomListService.postAllRooms(listOf(roomSummary))
|
||||||
val store = InMemoryAppPreferencesStore()
|
val onAnnouncementDismissedResult = lambdaRecorder<Announcement, Unit> { }
|
||||||
|
val announcementService = FakeAnnouncementService(
|
||||||
|
onAnnouncementDismissedResult = onAnnouncementDismissedResult,
|
||||||
|
)
|
||||||
val presenter = createRoomListPresenter(
|
val presenter = createRoomListPresenter(
|
||||||
client = matrixClient,
|
client = matrixClient,
|
||||||
appPreferencesStore = store,
|
announcementService = announcementService,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
assertThat(announcementService.announcementsToShowFlow().first()).isEmpty()
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||||
store.setShowNewNotificationSoundBanner(true)
|
announcementService.emitAnnouncementsToShow(listOf(Announcement.NewNotificationSound))
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isTrue()
|
|
||||||
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue()
|
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue()
|
||||||
state.eventSink(RoomListEvents.DismissNewNotificationSoundBanner)
|
state.eventSink(RoomListEvents.DismissNewNotificationSoundBanner)
|
||||||
|
onAnnouncementDismissedResult.assertions().isCalledOnce()
|
||||||
|
.with(value(Announcement.NewNotificationSound))
|
||||||
|
// Simulate service updating the value
|
||||||
|
announcementService.emitAnnouncementsToShow(emptyList())
|
||||||
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||||
// Ensure store has been updated
|
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,6 +646,7 @@ class RoomListPresenterTest {
|
||||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||||
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
|
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
|
||||||
|
announcementService: AnnouncementService = FakeAnnouncementService(),
|
||||||
) = RoomListPresenter(
|
) = RoomListPresenter(
|
||||||
client = client,
|
client = client,
|
||||||
leaveRoomPresenter = { leaveRoomState },
|
leaveRoomPresenter = { leaveRoomState },
|
||||||
|
|
@ -663,5 +671,6 @@ class RoomListPresenterTest {
|
||||||
notificationCleaner = notificationCleaner,
|
notificationCleaner = notificationCleaner,
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
seenInvitesStore = seenInvitesStore,
|
seenInvitesStore = seenInvitesStore,
|
||||||
|
announcementService = announcementService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ android {
|
||||||
setupDependencyInjection()
|
setupDependencyInjection()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(projects.features.announcement.api)
|
||||||
implementation(projects.features.migration.api)
|
implementation(projects.features.migration.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
|
|
@ -34,5 +35,6 @@ dependencies {
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.sessionStorage.test)
|
testImplementation(projects.libraries.sessionStorage.test)
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
|
testImplementation(projects.features.announcement.test)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ package io.element.android.features.migration.impl.migrations
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesIntoSet
|
import dev.zacsweers.metro.ContributesIntoSet
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.announcement.api.AnnouncementService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the new notification sound banner is displayed, but only on application upgrade.
|
* Ensure the new notification sound banner is displayed, but only on application upgrade.
|
||||||
|
|
@ -18,13 +19,13 @@ import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
@ContributesIntoSet(AppScope::class)
|
@ContributesIntoSet(AppScope::class)
|
||||||
@Inject
|
@Inject
|
||||||
class AppMigration08(
|
class AppMigration08(
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val announcementService: AnnouncementService,
|
||||||
) : AppMigration {
|
) : AppMigration {
|
||||||
override val order: Int = 8
|
override val order: Int = 8
|
||||||
|
|
||||||
override suspend fun migrate(isFreshInstall: Boolean) {
|
override suspend fun migrate(isFreshInstall: Boolean) {
|
||||||
if (!isFreshInstall) {
|
if (!isFreshInstall) {
|
||||||
appPreferencesStore.setShowNewNotificationSoundBanner(true)
|
announcementService.showAnnouncement(Announcement.NewNotificationSound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,35 @@
|
||||||
package io.element.android.features.migration.impl.migrations
|
package io.element.android.features.migration.impl.migrations
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
import io.element.android.features.announcement.api.Announcement
|
||||||
|
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class AppMigration08Test {
|
class AppMigration08Test {
|
||||||
@Test
|
@Test
|
||||||
fun `migration on fresh install should not modify the store`() = runTest {
|
fun `migration on fresh install should not invoke the AnnouncementService`() = runTest {
|
||||||
val store = InMemoryAppPreferencesStore()
|
val service = FakeAnnouncementService(
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
showAnnouncementResult = { lambdaError() },
|
||||||
val migration = AppMigration08(store)
|
)
|
||||||
|
val migration = AppMigration08(service)
|
||||||
migration.migrate(isFreshInstall = true)
|
migration.migrate(isFreshInstall = true)
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
assertThat(service.announcementsToShowFlow().first()).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `migration on upgrade should modify the store`() = runTest {
|
fun `migration on upgrade should invoke the AnnouncementService`() = runTest {
|
||||||
val store = InMemoryAppPreferencesStore()
|
val showAnnouncementResult = lambdaRecorder<Announcement, Unit> { }
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isFalse()
|
val service = FakeAnnouncementService(
|
||||||
val migration = AppMigration08(store)
|
showAnnouncementResult = showAnnouncementResult,
|
||||||
|
)
|
||||||
|
val migration = AppMigration08(service)
|
||||||
migration.migrate(isFreshInstall = false)
|
migration.migrate(isFreshInstall = false)
|
||||||
assertThat(store.showNewNotificationSoundBanner().first()).isTrue()
|
showAnnouncementResult.assertions().isCalledOnce()
|
||||||
|
.with(value(Announcement.NewNotificationSound))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,5 @@ interface AppPreferencesStore {
|
||||||
suspend fun setTracingLogPacks(targets: Set<TraceLogPack>)
|
suspend fun setTracingLogPacks(targets: Set<TraceLogPack>)
|
||||||
fun getTracingLogPacksFlow(): Flow<Set<TraceLogPack>>
|
fun getTracingLogPacksFlow(): Flow<Set<TraceLogPack>>
|
||||||
|
|
||||||
suspend fun setShowNewNotificationSoundBanner(show: Boolean)
|
|
||||||
fun showNewNotificationSoundBanner(): Flow<Boolean>
|
|
||||||
|
|
||||||
suspend fun reset()
|
suspend fun reset()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ private val hideInviteAvatarsKey = booleanPreferencesKey("hideInviteAvatars")
|
||||||
private val timelineMediaPreviewValueKey = stringPreferencesKey("timelineMediaPreviewValue")
|
private val timelineMediaPreviewValueKey = stringPreferencesKey("timelineMediaPreviewValue")
|
||||||
private val logLevelKey = stringPreferencesKey("logLevel")
|
private val logLevelKey = stringPreferencesKey("logLevel")
|
||||||
private val traceLogPacksKey = stringPreferencesKey("traceLogPacks")
|
private val traceLogPacksKey = stringPreferencesKey("traceLogPacks")
|
||||||
private val showNewNotificationSoundBannerKey = booleanPreferencesKey("showNewNotificationSoundBanner")
|
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -146,19 +145,6 @@ class DefaultAppPreferencesStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setShowNewNotificationSoundBanner(show: Boolean) {
|
|
||||||
store.edit { prefs ->
|
|
||||||
prefs[showNewNotificationSoundBannerKey] = show
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showNewNotificationSoundBanner(): Flow<Boolean> {
|
|
||||||
return store.data.map { prefs ->
|
|
||||||
// Default is false, but a migration will set it to true on application upgrade (see AppMigration08)
|
|
||||||
prefs[showNewNotificationSoundBannerKey] ?: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun reset() {
|
override suspend fun reset() {
|
||||||
store.edit { it.clear() }
|
store.edit { it.clear() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ class InMemoryAppPreferencesStore(
|
||||||
theme: String? = null,
|
theme: String? = null,
|
||||||
logLevel: LogLevel = LogLevel.INFO,
|
logLevel: LogLevel = LogLevel.INFO,
|
||||||
traceLockPacks: Set<TraceLogPack> = emptySet(),
|
traceLockPacks: Set<TraceLogPack> = emptySet(),
|
||||||
showNewNotificationSoundBanner: Boolean = false,
|
|
||||||
) : AppPreferencesStore {
|
) : AppPreferencesStore {
|
||||||
private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled)
|
private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled)
|
||||||
private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl)
|
private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl)
|
||||||
|
|
@ -31,7 +30,6 @@ class InMemoryAppPreferencesStore(
|
||||||
private val tracingLogPacks = MutableStateFlow(traceLockPacks)
|
private val tracingLogPacks = MutableStateFlow(traceLockPacks)
|
||||||
private val hideInviteAvatars = MutableStateFlow(hideInviteAvatars)
|
private val hideInviteAvatars = MutableStateFlow(hideInviteAvatars)
|
||||||
private val timelineMediaPreviewValue = MutableStateFlow(timelineMediaPreviewValue)
|
private val timelineMediaPreviewValue = MutableStateFlow(timelineMediaPreviewValue)
|
||||||
private val showNewNotificationSoundBanner = MutableStateFlow(showNewNotificationSoundBanner)
|
|
||||||
|
|
||||||
override suspend fun setDeveloperModeEnabled(enabled: Boolean) {
|
override suspend fun setDeveloperModeEnabled(enabled: Boolean) {
|
||||||
isDeveloperModeEnabled.value = enabled
|
isDeveloperModeEnabled.value = enabled
|
||||||
|
|
@ -93,14 +91,6 @@ class InMemoryAppPreferencesStore(
|
||||||
return tracingLogPacks
|
return tracingLogPacks
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setShowNewNotificationSoundBanner(show: Boolean) {
|
|
||||||
showNewNotificationSoundBanner.value = show
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showNewNotificationSoundBanner(): Flow<Boolean> {
|
|
||||||
return showNewNotificationSoundBanner
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun reset() {
|
override suspend fun reset() {
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue