Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add settings change notifier #8813

Merged
merged 2 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.fsck.k9.preferences

import app.k9mail.legacy.account.AccountManager
import app.k9mail.legacy.preferences.DefaultSettingsChangeBroker
import app.k9mail.legacy.preferences.GeneralSettingsManager
import app.k9mail.legacy.preferences.SettingsChangeBroker
import app.k9mail.legacy.preferences.SettingsChangePublisher
import com.fsck.k9.Preferences
import org.koin.core.qualifier.named
import org.koin.dsl.bind
import org.koin.dsl.binds
import org.koin.dsl.module

val preferencesModule = module {
Expand All @@ -24,12 +28,14 @@ val preferencesModule = module {
RealGeneralSettingsManager(
preferences = get(),
coroutineScope = get(named("AppCoroutineScope")),
changePublisher = get(),
)
} bind GeneralSettingsManager::class
single {
RealDrawerConfigManager(
preferences = get(),
coroutineScope = get(named("AppCoroutineScope")),
changeBroker = get(),
)
} bind DrawerConfigManager::class

Expand Down Expand Up @@ -77,4 +83,7 @@ val preferencesModule = module {
unifiedInboxConfigurator = get(),
)
}

single { DefaultSettingsChangeBroker() }
.binds(arrayOf(SettingsChangePublisher::class, SettingsChangeBroker::class))
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
package com.fsck.k9.preferences

import app.k9mail.feature.navigation.drawer.NavigationDrawerExternalContract.DrawerConfig
import app.k9mail.legacy.preferences.SettingsChangeBroker
import app.k9mail.legacy.preferences.SettingsChangeSubscriber
import com.fsck.k9.K9
import com.fsck.k9.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch

internal class RealDrawerConfigManager(
private val preferences: Preferences,
private val coroutineScope: CoroutineScope,
private val changeBroker: SettingsChangeBroker,
) : DrawerConfigManager {
private val drawerConfigFlow = MutableSharedFlow<DrawerConfig>(replay = 1)
private var drawerConfig: DrawerConfig? = null

init {
coroutineScope.launch {
asSettingsFlow().collect { config ->
drawerConfigFlow.emit(config)
}
}
}

override fun save(config: DrawerConfig) {
saveDrawerConfig(config)
updateDrawerConfigFlow(config)
}

private fun loadDrawerConfig(): DrawerConfig {
val drawerConfig = DrawerConfig(
return DrawerConfig(
showAccountSelector = K9.isShowAccountSelector,
showStarredCount = K9.isShowStarredCount,
showUnifiedFolders = K9.isShowUnifiedInbox,
)

updateDrawerConfigFlow(drawerConfig)

return drawerConfig
}

private fun updateDrawerConfigFlow(config: DrawerConfig) {
Expand All @@ -41,14 +49,31 @@ internal class RealDrawerConfigManager(

@Synchronized
override fun getConfig(): DrawerConfig {
return drawerConfig ?: loadDrawerConfig().also { drawerConfig = it }
return loadDrawerConfig().also {
updateDrawerConfigFlow(it)
}
}

override fun getConfigFlow(): Flow<DrawerConfig> {
getConfig()
return drawerConfigFlow.distinctUntilChanged()
}

private fun asSettingsFlow(): Flow<DrawerConfig> {
return callbackFlow {
send(loadDrawerConfig())

val subscriber = SettingsChangeSubscriber {
drawerConfigFlow.tryEmit(loadDrawerConfig())
}

changeBroker.subscribe(subscriber)

awaitClose {
changeBroker.unsubscribe(subscriber)
}
}
}

@Synchronized
private fun saveDrawerConfig(config: DrawerConfig) {
val editor = preferences.createStorageEditor()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import app.k9mail.legacy.preferences.AppTheme
import app.k9mail.legacy.preferences.BackgroundSync
import app.k9mail.legacy.preferences.GeneralSettings
import app.k9mail.legacy.preferences.GeneralSettingsManager
import app.k9mail.legacy.preferences.SettingsChangePublisher
import app.k9mail.legacy.preferences.SubTheme
import com.fsck.k9.K9
import com.fsck.k9.Preferences
Expand All @@ -30,6 +31,7 @@ import timber.log.Timber
internal class RealGeneralSettingsManager(
private val preferences: Preferences,
private val coroutineScope: CoroutineScope,
private val changePublisher: SettingsChangePublisher,
private val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : GeneralSettingsManager {
private val settingsFlow = MutableSharedFlow<GeneralSettings>(replay = 1)
Expand Down Expand Up @@ -88,6 +90,8 @@ internal class RealGeneralSettingsManager(
K9.save(editor)
writeSettings(editor, settings)
editor.commit()

changePublisher.publish()
}

@Synchronized
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package app.k9mail.legacy.preferences

class DefaultSettingsChangeBroker(
private val subscribers: MutableSet<SettingsChangeSubscriber> = mutableSetOf(),
) : SettingsChangeBroker, SettingsChangePublisher {

private val lock = Any()

override fun subscribe(subscriber: SettingsChangeSubscriber) {
synchronized(lock) {
subscribers.add(subscriber)
}
}

override fun unsubscribe(subscriber: SettingsChangeSubscriber) {
synchronized(lock) {
subscribers.remove(subscriber)
}
}

override fun publish() {
val currentSubscribers = synchronized(lock) { HashSet(subscribers) }

for (subscriber in currentSubscribers) {
subscriber.receive()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package app.k9mail.legacy.preferences

/**
* Broker to manage subscribers and notify them about changes in the settings, when the
* [SettingsChangePublisher] publishes a change.
*/
interface SettingsChangeBroker {

/**
* Subscribe to settings changes.
*
* @param subscriber The subscriber to be notified about settings changes.
*/
fun subscribe(subscriber: SettingsChangeSubscriber)

/**
* Unsubscribe from settings changes.
*
* @param subscriber The subscriber that no longer wants to be notified about settings changes.
*/
fun unsubscribe(subscriber: SettingsChangeSubscriber)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app.k9mail.legacy.preferences

/**
* Publishes changes in the settings.
*/
interface SettingsChangePublisher {

/**
* Publish a change in the settings.
*/
fun publish()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app.k9mail.legacy.preferences

/**
* Subscribe to be notified about changes in the settings.
*/
fun interface SettingsChangeSubscriber {

/**
* Called when settings change.
*/
fun receive()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package app.k9mail.legacy.preferences

import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.doesNotContain
import assertk.assertions.isEqualTo
import kotlin.test.Test

class DefaultSettingsChangeBrokerTest {

@Test
fun `subscribe should add subscriber`() {
val subscriber = SettingsChangeSubscriber { }
val subscribers = mutableSetOf<SettingsChangeSubscriber>()
val broker = DefaultSettingsChangeBroker(subscribers)

broker.subscribe(subscriber)

assertThat(subscribers.size).isEqualTo(1)
assertThat(subscribers).contains(subscriber)
}

@Test
fun `unsubscribe should remove subscriber`() {
val subscriber = SettingsChangeSubscriber { }
val subscribers = mutableSetOf<SettingsChangeSubscriber>(subscriber)
val broker = DefaultSettingsChangeBroker(subscribers)

broker.unsubscribe(subscriber)

assertThat(subscribers.size).isEqualTo(0)
assertThat(subscribers).doesNotContain(subscriber)
}

@Test
fun `publish should notify subscribers`() {
var received = false
val subscriber = SettingsChangeSubscriber { received = true }
var receivedOther = false
val otherSubscriber = SettingsChangeSubscriber { receivedOther = true }
val subscribers = mutableSetOf<SettingsChangeSubscriber>(subscriber, otherSubscriber)
val broker = DefaultSettingsChangeBroker(subscribers)

broker.publish()

assertThat(received).isEqualTo(true)
assertThat(receivedOther).isEqualTo(true)
}
}