Skip to content

Commit 9ed7886

Browse files
authored
Refresh sync settings screen automatically (#5773)
Task/Issue URL: https://app.asana.com/1/137249556945/project/608920331025315/task/1209669301264637 ### Description Allows the sync settings screen to automatically refresh while it is being viewed. This is in part to provide a better UX to #5762. ### Steps to test this PR - [x] Enter sync settings with sync disabled. Verify everything looks as expected and there is no visual updates to the UI - [x] Enable sync. Verify everything looks as expected and there is no visual updates to the UI. - [x] Sync with another device, such that you can see two devices in this view; your device, plus one other - [x] On the other device, turn off sync - [x] Back on the first device, verify it updates within the next 5s to show the other device is now gone - [x] Disable `sync / automaticallyUpdateSyncSettings` feature flag and return to the sync settings screen. Repeat the above test and verify the other device doesn’t automatically update since background refreshing is disabled (i.e., fallback to original behaviour when flag disabled) - [x] Smoke test any other related sync scenarios you think might be problematic because of this Co-authored-by: Craig Russell <[email protected]>
1 parent c678f2e commit 9ed7886

File tree

4 files changed

+52
-14
lines changed

4 files changed

+52
-14
lines changed

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,11 @@ interface SyncFeature {
4747

4848
@Toggle.DefaultValue(true)
4949
fun seamlessAccountSwitching(): Toggle
50+
51+
@InternalAlwaysEnabled
52+
@Toggle.DefaultValue(false)
53+
fun exchangeKeysToSyncWithAnotherDevice(): Toggle
54+
55+
@Toggle.DefaultValue(true)
56+
fun automaticallyUpdateSyncSettings(): Toggle
5057
}

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeatureToggle.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ interface SyncFeatureToggle {
5050
fun allowCreateAccount(): Boolean
5151

5252
fun allowCreateAccountOnNewerVersion(): Boolean
53+
54+
fun automaticallyUpdateSyncSettings(): Boolean
5355
}
5456

5557
@ContributesBinding(
@@ -111,6 +113,10 @@ class SyncRemoteFeatureToggle @Inject constructor(
111113
return isToggleEnabledOnNewerVersion(syncFeature.level3AllowCreateAccount())
112114
}
113115

116+
override fun automaticallyUpdateSyncSettings(): Boolean {
117+
return syncFeature.automaticallyUpdateSyncSettings().isEnabled()
118+
}
119+
114120
private fun isToggleEnabledOnNewerVersion(toggle: Toggle): Boolean {
115121
val rawStoredState = toggle.getRawStoredState()
116122

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncActivityViewModel.kt

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.annotation.StringRes
2121
import androidx.lifecycle.ViewModel
2222
import androidx.lifecycle.viewModelScope
2323
import com.duckduckgo.anvil.annotations.ContributesViewModel
24+
import com.duckduckgo.common.utils.ConflatedJob
2425
import com.duckduckgo.common.utils.DispatcherProvider
2526
import com.duckduckgo.di.scopes.ActivityScope
2627
import com.duckduckgo.sync.api.SyncState.OFF
@@ -56,13 +57,15 @@ import java.io.File
5657
import javax.inject.Inject
5758
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
5859
import kotlinx.coroutines.channels.Channel
60+
import kotlinx.coroutines.delay
5961
import kotlinx.coroutines.flow.Flow
6062
import kotlinx.coroutines.flow.MutableStateFlow
6163
import kotlinx.coroutines.flow.flowOn
6264
import kotlinx.coroutines.flow.launchIn
6365
import kotlinx.coroutines.flow.onEach
6466
import kotlinx.coroutines.flow.onStart
6567
import kotlinx.coroutines.flow.receiveAsFlow
68+
import kotlinx.coroutines.isActive
6669
import kotlinx.coroutines.launch
6770
import kotlinx.coroutines.withContext
6871

@@ -78,6 +81,9 @@ class SyncActivityViewModel @Inject constructor(
7881
private val syncPixels: SyncPixels,
7982
) : ViewModel() {
8083

84+
private var syncStateObserverJob = ConflatedJob()
85+
private var backgroundRefreshJob = ConflatedJob()
86+
8187
private val command = Channel<Command>(1, DROP_OLDEST)
8288
private val viewState = MutableStateFlow(ViewState())
8389
fun commands(): Flow<Command> = command.receiveAsFlow().onStart {
@@ -90,21 +96,34 @@ class SyncActivityViewModel @Inject constructor(
9096
}.flowOn(dispatchers.io())
9197

9298
private fun observeState() {
93-
syncStateMonitor.syncState().onEach { syncState ->
94-
val state = if (syncState == OFF) {
95-
signedOutState()
96-
} else {
97-
signedInState()
98-
}
99-
viewState.value = state
100-
}.onStart {
101-
initViewStateThisDeviceState()
102-
fetchRemoteDevices()
103-
syncEngine.triggerSync(FEATURE_READ)
104-
}.flowOn(dispatchers.io())
99+
syncStateObserverJob += syncStateMonitor.syncState()
100+
.onEach { syncState ->
101+
val state = if (syncState == OFF) {
102+
signedOutState()
103+
} else {
104+
signedInState()
105+
}
106+
viewState.value = state
107+
}.onStart {
108+
initViewStateThisDeviceState()
109+
fetchRemoteDevices()
110+
syncEngine.triggerSync(FEATURE_READ)
111+
schedulePeriodicRefresh()
112+
}.flowOn(dispatchers.io())
105113
.launchIn(viewModelScope)
106114
}
107115

116+
private fun schedulePeriodicRefresh() {
117+
backgroundRefreshJob += viewModelScope.launch(dispatchers.io()) {
118+
while (isActive && syncFeatureToggle.automaticallyUpdateSyncSettings()) {
119+
delay(SETTINGS_REFRESH_RATE_MS)
120+
if (syncAccountRepository.isSignedIn()) {
121+
fetchRemoteDevices(showLoadingState = false)
122+
}
123+
}
124+
}
125+
}
126+
108127
private suspend fun checkIfDeviceSupported() {
109128
val isSupported = withContext(dispatchers.io()) {
110129
syncAccountRepository.isSyncSupported()
@@ -215,8 +234,11 @@ class SyncActivityViewModel @Inject constructor(
215234
}
216235
}
217236

218-
private suspend fun fetchRemoteDevices() {
219-
viewState.value = viewState.value.showDeviceListItemLoading()
237+
private suspend fun fetchRemoteDevices(showLoadingState: Boolean = true) {
238+
if (showLoadingState) {
239+
viewState.value = viewState.value.showDeviceListItemLoading()
240+
}
241+
220242
val result = withContext(dispatchers.io()) {
221243
syncAccountRepository.getConnectedDevices()
222244
}
@@ -412,5 +434,6 @@ class SyncActivityViewModel @Inject constructor(
412434
companion object {
413435
private const val SOURCE_SYNC_DISABLED = "not_activated"
414436
private const val SOURCE_SYNC_ENABLED = "activated"
437+
private const val SETTINGS_REFRESH_RATE_MS = 5_000L
415438
}
416439
}

sync/sync-impl/src/test/java/com/duckduckgo/sync/impl/ui/SyncDisabledViewModelTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,13 @@ class FakeSyncFeatureToggle : SyncFeatureToggle {
262262
var allowSetupFlowsOnNewerVersion: Boolean = true
263263
var allowCreateAccount: Boolean = true
264264
var allowCreateAccountOnNewerVersion: Boolean = true
265+
var automaticallyUpdateSyncSettings: Boolean = true
265266
override fun showSync() = showSync
266267
override fun allowDataSyncing() = allowDataSyncing
267268
override fun allowDataSyncingOnNewerVersion() = allowDataSyncingOnNewerVersion
268269
override fun allowSetupFlows() = allowSetupFlows
269270
override fun allowSetupFlowsOnNewerVersion() = allowSetupFlowsOnNewerVersion
270271
override fun allowCreateAccount() = allowCreateAccount
271272
override fun allowCreateAccountOnNewerVersion() = allowCreateAccountOnNewerVersion
273+
override fun automaticallyUpdateSyncSettings() = automaticallyUpdateSyncSettings
272274
}

0 commit comments

Comments
 (0)