refactor: Move nudge persistence into GeneralSettings

Consolidate the nudge availability DataStoreValue with the rest of the persisted settings instead of carrying it in a separate Hilt module + DataStore file. Matches the existing convention where compat-style flags (offloaded filtering, indirect callbacks, etc.) live alongside theme/notification settings.

NudgeCapabilityStore keeps the verdict-mapping behavior; only the source of the persisted value changes.
This commit is contained in:
Matthias Urhahn
2026-05-07 14:10:16 +02:00
committed by Matthias Urhahn
parent 0e6848d886
commit 83ef111599
4 changed files with 19 additions and 38 deletions
@@ -1,35 +0,0 @@
package eu.darken.capod.common.bluetooth
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import eu.darken.capod.common.datastore.DataStoreValue
import eu.darken.capod.common.datastore.createValue
import eu.darken.capod.common.serialization.SerializationCapod
import kotlinx.serialization.json.Json
import javax.inject.Singleton
private val Context.bluetoothDataStore: DataStore<Preferences> by preferencesDataStore(name = "bluetooth_state")
@InstallIn(SingletonComponent::class)
@Module
object BluetoothPersistenceModule {
@Provides
@Singleton
fun provideNudgeAvailability(
@ApplicationContext context: Context,
@SerializationCapod json: Json,
): DataStoreValue<NudgeAvailability> = context.bluetoothDataStore.createValue(
key = "core.bluetooth.nudge.availability",
defaultValue = NudgeAvailability.UNKNOWN,
json = json,
onErrorFallbackToDefault = true,
)
}
@@ -2,12 +2,12 @@ package eu.darken.capod.common.bluetooth
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.datastore.DataStoreValue
import eu.darken.capod.common.datastore.value
import eu.darken.capod.common.datastore.valueBlocking
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.main.core.GeneralSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -19,11 +19,13 @@ import javax.inject.Singleton
@Singleton
class NudgeCapabilityStore @Inject constructor(
private val persistedValue: DataStoreValue<NudgeAvailability>,
generalSettings: GeneralSettings,
@AppScope private val appScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
) {
private val persistedValue = generalSettings.nudgeAvailability
// Resolve the persisted value synchronously at construction so consumers that read
// `availability.value` (resolver, AutoConnect precondition, UI state) never see the
// synthetic UNKNOWN seed before the first DataStore emission arrives. On a known-broken
@@ -9,6 +9,7 @@ import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.bluetooth.NudgeAvailability
import eu.darken.capod.common.datastore.createValue
import eu.darken.capod.common.serialization.ByteArrayBase64Serializer
import eu.darken.capod.common.serialization.SerializationCapod
@@ -72,6 +73,12 @@ class GeneralSettings @Inject constructor(
val isOffloadedBatchingDisabled = dataStore.createValue("core.compat.offloaded.batching.disabled", false)
val useIndirectScanResultCallback = dataStore.createValue("core.compat.indirectcallback.enabled", false)
/** Runtime-detected verdict for whether `BluetoothHeadset.connect()` reflection works on this device. */
val nudgeAvailability = dataStore.createValue(
"core.bluetooth.nudge.availability", NudgeAvailability.UNKNOWN, json,
onErrorFallbackToDefault = true,
)
val isOnboardingDone = dataStore.createValue("core.onboarding.done", false)
val reactionsHintDismissed = dataStore.createValue("ui.hint.reactions_per_device.dismissed", false)
@@ -1,6 +1,9 @@
package eu.darken.capod.common.bluetooth
import eu.darken.capod.main.core.GeneralSettings
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
@@ -18,11 +21,15 @@ class NudgeCapabilityStoreTest : BaseTest() {
private val testDispatcher = UnconfinedTestDispatcher()
private lateinit var fakePersisted: FakeDataStoreValue<NudgeAvailability>
private lateinit var generalSettings: GeneralSettings
@BeforeEach
fun setup() {
Dispatchers.setMain(testDispatcher)
fakePersisted = FakeDataStoreValue(NudgeAvailability.UNKNOWN)
generalSettings = mockk<GeneralSettings>().also {
every { it.nudgeAvailability } returns fakePersisted.mock
}
}
@AfterEach
@@ -31,7 +38,7 @@ class NudgeCapabilityStoreTest : BaseTest() {
}
private fun createStore() = NudgeCapabilityStore(
persistedValue = fakePersisted.mock,
generalSettings = generalSettings,
appScope = kotlinx.coroutines.CoroutineScope(testDispatcher),
dispatcherProvider = TestDispatcherProvider(testDispatcher),
)