Skip to content

Commit d78c758

Browse files
committed
Handle user settings logic through the status checker
1 parent 4dbeef2 commit d78c758

5 files changed

Lines changed: 66 additions & 13 deletions

File tree

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/AdBlockingSettingsRepository.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import kotlinx.coroutines.flow.Flow
2424
import javax.inject.Inject
2525

2626
interface AdBlockingSettingsRepository {
27-
fun isEnabledFlow(): Flow<Boolean>
27+
28+
fun isEnabledFlow(): Flow<Boolean?>
2829
suspend fun setEnabled(enabled: Boolean)
2930
}
3031

@@ -34,7 +35,7 @@ class RealAdBlockingSettingsRepository @Inject constructor(
3435
private val userPreferences: AdBlockingUserPreferences,
3536
) : AdBlockingSettingsRepository {
3637

37-
override fun isEnabledFlow(): Flow<Boolean> = userPreferences.isEnabledFlow()
38+
override fun isEnabledFlow(): Flow<Boolean?> = userPreferences.isEnabledFlow()
3839

3940
override suspend fun setEnabled(enabled: Boolean) {
4041
userPreferences.setEnabled(enabled)

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/domain/AdBlockingStatusChecker.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,19 @@ import com.duckduckgo.di.scopes.AppScope
2323
import com.squareup.anvil.annotations.ContributesBinding
2424
import dagger.SingleInstanceIn
2525
import kotlinx.coroutines.CoroutineScope
26+
import kotlinx.coroutines.flow.Flow
2627
import kotlinx.coroutines.flow.SharingStarted
2728
import kotlinx.coroutines.flow.StateFlow
29+
import kotlinx.coroutines.flow.map
2830
import kotlinx.coroutines.flow.stateIn
2931
import logcat.logcat
3032
import javax.inject.Inject
3133

3234
interface AdBlockingStatusChecker {
3335
fun canInject(): Boolean
3436
fun isShownInSettings(): Boolean
37+
38+
fun isUserEnabledFlow(): Flow<Boolean>
3539
}
3640

3741
@SingleInstanceIn(AppScope::class)
@@ -43,6 +47,7 @@ class RealAdBlockingStatusChecker @Inject constructor(
4347
) : AdBlockingStatusChecker {
4448

4549
private val userEnabled: StateFlow<Boolean> = settingsRepository.isEnabledFlow()
50+
.map { it ?: feature.enabledByDefault().isEnabled() }
4651
.stateIn(appScope, SharingStarted.Eagerly, initialValue = false)
4752

4853
override fun canInject(): Boolean {
@@ -62,4 +67,6 @@ class RealAdBlockingStatusChecker @Inject constructor(
6267
}
6368

6469
override fun isShownInSettings(): Boolean = feature.isDiscoverable().isEnabled()
70+
71+
override fun isUserEnabledFlow(): Flow<Boolean> = userEnabled
6572
}

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/store/AdBlockingUserPreferences.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.datastore.preferences.core.Preferences
2121
import androidx.datastore.preferences.core.booleanPreferencesKey
2222
import androidx.datastore.preferences.core.edit
2323
import com.duckduckgo.adblocking.impl.di.AdBlockingPreferences
24-
import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature
2524
import com.duckduckgo.di.scopes.AppScope
2625
import com.squareup.anvil.annotations.ContributesBinding
2726
import dagger.SingleInstanceIn
@@ -31,23 +30,21 @@ import kotlinx.coroutines.flow.map
3130
import javax.inject.Inject
3231

3332
interface AdBlockingUserPreferences {
34-
fun isEnabledFlow(): Flow<Boolean>
35-
suspend fun isEnabled(): Boolean
33+
34+
fun isEnabledFlow(): Flow<Boolean?>
35+
suspend fun isEnabled(): Boolean?
3636
suspend fun setEnabled(enabled: Boolean)
3737
}
3838

3939
@SingleInstanceIn(AppScope::class)
4040
@ContributesBinding(AppScope::class)
4141
class RealAdBlockingUserPreferences @Inject constructor(
4242
@AdBlockingPreferences private val dataStore: DataStore<Preferences>,
43-
private val feature: AdBlockingExtensionFeature,
4443
) : AdBlockingUserPreferences {
4544

46-
override fun isEnabledFlow(): Flow<Boolean> = dataStore.data.map { prefs ->
47-
prefs[KEY_ENABLED] ?: feature.enabledByDefault().isEnabled()
48-
}
45+
override fun isEnabledFlow(): Flow<Boolean?> = dataStore.data.map { prefs -> prefs[KEY_ENABLED] }
4946

50-
override suspend fun isEnabled(): Boolean = isEnabledFlow().first()
47+
override suspend fun isEnabled(): Boolean? = isEnabledFlow().first()
5148

5249
override suspend fun setEnabled(enabled: Boolean) {
5350
dataStore.edit { prefs -> prefs[KEY_ENABLED] = enabled }

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/ui/AdBlockingSettingsViewModel.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.duckduckgo.adblocking.impl.ui
1919
import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.viewModelScope
2121
import com.duckduckgo.adblocking.impl.AdBlockingSettingsRepository
22+
import com.duckduckgo.adblocking.impl.domain.AdBlockingStatusChecker
2223
import com.duckduckgo.anvil.annotations.ContributesViewModel
2324
import com.duckduckgo.di.scopes.ActivityScope
2425
import kotlinx.coroutines.channels.BufferOverflow
@@ -34,6 +35,7 @@ import javax.inject.Inject
3435

3536
@ContributesViewModel(ActivityScope::class)
3637
class AdBlockingSettingsViewModel @Inject constructor(
38+
private val statusChecker: AdBlockingStatusChecker,
3739
private val repository: AdBlockingSettingsRepository,
3840
) : ViewModel() {
3941

@@ -44,7 +46,7 @@ class AdBlockingSettingsViewModel @Inject constructor(
4446
data object OpenDuckPlayerSettings : Command()
4547
}
4648

47-
val viewState: StateFlow<ViewState> = repository.isEnabledFlow()
49+
val viewState: StateFlow<ViewState> = statusChecker.isUserEnabledFlow()
4850
.map { ViewState(isEnabled = it) }
4951
.stateIn(
5052
viewModelScope,

ad-blocking/ad-blocking-impl/src/test/java/com/duckduckgo/adblocking/impl/domain/RealAdBlockingStatusCheckerTest.kt

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
2424
import kotlinx.coroutines.cancel
2525
import kotlinx.coroutines.flow.Flow
2626
import kotlinx.coroutines.flow.MutableStateFlow
27+
import kotlinx.coroutines.flow.first
2728
import kotlinx.coroutines.test.UnconfinedTestDispatcher
29+
import kotlinx.coroutines.test.runTest
2830
import org.junit.After
2931
import org.junit.Assert.assertFalse
3032
import org.junit.Assert.assertTrue
@@ -38,21 +40,26 @@ class RealAdBlockingStatusCheckerTest {
3840

3941
private var discoverableEnabled = true
4042
private var operationalEnabled = true
43+
private var enabledByDefault = false
4144

4245
private val isDiscoverableToggle: Toggle = mock {
4346
on { isEnabled() } doAnswer { discoverableEnabled }
4447
}
4548
private val selfToggle: Toggle = mock {
4649
on { isEnabled() } doAnswer { operationalEnabled }
4750
}
51+
private val enabledByDefaultToggle: Toggle = mock {
52+
on { isEnabled() } doAnswer { enabledByDefault }
53+
}
4854
private val feature: AdBlockingExtensionFeature = mock {
4955
on { isDiscoverable() } doReturn isDiscoverableToggle
5056
on { self() } doReturn selfToggle
57+
on { enabledByDefault() } doReturn enabledByDefaultToggle
5158
}
5259

53-
private val userEnabledFlow = MutableStateFlow(true)
60+
private val userEnabledFlow = MutableStateFlow<Boolean?>(true)
5461
private val settingsRepository: AdBlockingSettingsRepository = object : AdBlockingSettingsRepository {
55-
override fun isEnabledFlow(): Flow<Boolean> = userEnabledFlow
62+
override fun isEnabledFlow(): Flow<Boolean?> = userEnabledFlow
5663
override suspend fun setEnabled(enabled: Boolean) {
5764
userEnabledFlow.value = enabled
5865
}
@@ -94,6 +101,45 @@ class RealAdBlockingStatusCheckerTest {
94101
assertFalse(checker.canInject())
95102
}
96103

104+
@Test
105+
fun whenUserHasNoPreferenceAndEnabledByDefaultIsTrueThenCanInject() {
106+
userEnabledFlow.value = null
107+
enabledByDefault = true
108+
109+
assertTrue(checker.canInject())
110+
}
111+
112+
@Test
113+
fun whenUserHasNoPreferenceAndEnabledByDefaultIsFalseThenCannotInject() {
114+
userEnabledFlow.value = null
115+
enabledByDefault = false
116+
117+
assertFalse(checker.canInject())
118+
}
119+
120+
@Test
121+
fun whenUserHasSetTrueThenIsUserEnabledFlowEmitsTrue() = runTest {
122+
userEnabledFlow.value = true
123+
124+
assertTrue(checker.isUserEnabledFlow().first())
125+
}
126+
127+
@Test
128+
fun whenUserHasSetFalseThenIsUserEnabledFlowEmitsFalseEvenIfDefaultIsTrue() = runTest {
129+
userEnabledFlow.value = false
130+
enabledByDefault = true
131+
132+
assertFalse(checker.isUserEnabledFlow().first())
133+
}
134+
135+
@Test
136+
fun whenUserHasNoPreferenceThenIsUserEnabledFlowEmitsDefault() = runTest {
137+
userEnabledFlow.value = null
138+
enabledByDefault = true
139+
140+
assertTrue(checker.isUserEnabledFlow().first())
141+
}
142+
97143
@Test
98144
fun whenDiscoverableFlagEnabledThenIsShownInSettings() {
99145
assertTrue(checker.isShownInSettings())

0 commit comments

Comments
 (0)