e4607bb6696b

fix: Make vault destruction reset Sync state (v2.6.2)

time
2026-07-27 21:11 UTC · 2 months ago
machine
koh2
parent
fb46840009b2
10 files app/build.gradle+2 −2app/src/main/java/com/peachpasswords/sync/BackgroundSyncPreference.kt+5 −0app/src/main/java/com/peachpasswords/sync/DeviceIdentity.kt+7 −0app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+4 −1app/src/main/java/com/peachpasswords/sync/PendingVouchedDeviceStore.kt+4 −0app/src/main/java/com/peachpasswords/sync/RelaySettingsStore.kt+4 −0app/src/main/java/com/peachpasswords/sync/SyncFreshStartResetter.kt+54 −0app/src/main/java/com/peachpasswords/ui/lock/LockViewModel.kt+3 −0app/src/main/java/com/peachpasswords/ui/settings/PanicScreen.kt+3 −0app/src/test/java/com/peachpasswords/sync/SyncFreshStartResetterTest.kt+35 −0
app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616         applicationId "com.peachpasswords"1717         minSdk 261818         targetSdk 3519-        versionCode 24012520-        versionName "2.6.1"19+        versionCode 24012620+        versionName "2.6.2"2121 2222         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323         vectorDrawables {

view file

app/src/main/java/com/peachpasswords/sync/BackgroundSyncPreference.kt+5 −0
@@ -23,6 +23,11 @@2323         _enabled.value = enabled2424     }2525 26+    fun reset() {27+        prefs.edit().clear().apply()28+        _enabled.value = true29+    }30+2631     companion object {2732         private const val PREFS_FILE = "peach_background_sync"2833         private const val KEY_ENABLED = "enabled"

view file

app/src/main/java/com/peachpasswords/sync/DeviceIdentity.kt+7 −0
@@ -51,6 +51,13 @@5151         .apply()5252 }5353 54+fun clearDeviceIdentity(context: Context) {55+    context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)56+        .edit()57+        .clear()58+        .apply()59+}60+5461 fun buildLocalIdentity(context: Context, masterKey: ByteArray): LocalDeviceIdentity {5562     val deviceId = getOrCreateDeviceId(context)5663     val deviceName = getDeviceName(context)

view file

app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+4 −1
@@ -137,7 +137,10 @@137137     }138138 139139     fun clear() {140-        prefs.edit().remove(KEY_DEVICES).apply()140+        prefs.edit()141+            .remove(KEY_DEVICES)142+            .remove(KEY_REVOCATIONS)143+            .apply()141144     }142145 143146     /**

view file

app/src/main/java/com/peachpasswords/sync/PendingVouchedDeviceStore.kt+4 −0
@@ -66,6 +66,10 @@6666         persist(getAll().filterNot { it.deviceId == id })6767     }6868 69+    fun clear() {70+        persist(emptyList())71+    }72+6973     fun observeAll(): Flow<List<PendingVouchedDevice>> = callbackFlow {7074         trySend(getAll())7175         val listener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { _, key ->

view file

app/src/main/java/com/peachpasswords/sync/RelaySettingsStore.kt+4 −0
@@ -143,6 +143,10 @@143143         return normalized144144     }145145 146+    fun reset() {147+        prefs.edit().clear().apply()148+    }149+146150     private fun storedRelayUrl(): String = prefs.getString(KEY_URL, null)147151         ?.let { runCatching { Rendezvous.normalizeRelayUrl(it) }.getOrNull() }148152         ?: Rendezvous.DEFAULT_RELAY_URL

view file

app/src/main/java/com/peachpasswords/sync/SyncFreshStartResetter.kt+54 −0
@@ -0,0 +1,54 @@1+package com.peachpasswords.sync2+3+import android.content.Context4+import dagger.hilt.android.qualifiers.ApplicationContext5+import javax.inject.Inject6+import javax.inject.Singleton7+8+/**9+ * Clears every piece of state that can make a destroyed vault still look10+ * paired. The Pro license deliberately lives outside this boundary.11+ */12+@Singleton13+class SyncFreshStartResetter @Inject constructor(14+    @ApplicationContext private val context: Context,15+    private val devices: PairedDeviceStore,16+    private val pendingDevices: PendingVouchedDeviceStore,17+    private val alerts: SyncAlertStore,18+    private val outcomes: SyncOutcomeStore,19+    private val relaySettings: RelaySettingsStore,20+    private val backgroundSync: BackgroundSyncPreference,21+) {22+    fun reset() {23+        resetSyncStateForFreshStart(24+            stopService = { PeachSyncService.stop(context) },25+            clearDevices = devices::clear,26+            clearPendingDevices = pendingDevices::clear,27+            clearAlerts = alerts::clear,28+            clearOutcomes = outcomes::reset,29+            clearRelaySettings = relaySettings::reset,30+            clearBackgroundPreference = backgroundSync::reset,31+            clearLocalIdentity = { clearDeviceIdentity(context) },32+        )33+    }34+}35+36+internal fun resetSyncStateForFreshStart(37+    stopService: () -> Unit,38+    clearDevices: () -> Unit,39+    clearPendingDevices: () -> Unit,40+    clearAlerts: () -> Unit,41+    clearOutcomes: () -> Unit,42+    clearRelaySettings: () -> Unit,43+    clearBackgroundPreference: () -> Unit,44+    clearLocalIdentity: () -> Unit,45+) {46+    stopService()47+    clearDevices()48+    clearPendingDevices()49+    clearAlerts()50+    clearOutcomes()51+    clearRelaySettings()52+    clearBackgroundPreference()53+    clearLocalIdentity()54+}

view file

app/src/main/java/com/peachpasswords/ui/lock/LockViewModel.kt+3 −0
@@ -6,6 +6,7 @@66 import androidx.lifecycle.ViewModel77 import androidx.lifecycle.viewModelScope88 import com.peachpasswords.sync.ForegroundSyncCoordinator9+import com.peachpasswords.sync.SyncFreshStartResetter910 import com.peachpasswords.ui.PeachNav1011 import com.peachpasswords.ui.vault.entryTapDestination1112 import com.peachpasswords.vault.CreateVaultResult@@ -87,6 +88,7 @@8788     private val lockTimer: VaultLockTimer,8889     private val lastViewed: LastViewedEntryStore,8990     private val foregroundSync: ForegroundSyncCoordinator,91+    private val syncFreshStart: SyncFreshStartResetter,9092     @ApplicationContext private val appContext: Context,9193 ) : ViewModel() {9294     private val _state = MutableStateFlow(LockUiState(hasVault = vault.hasVault()))@@ -146,6 +148,7 @@146148             DuressAction.DESTROY -> {147149                 vault.discardDuressUnlock(duressUnlock)148150                 vault.destroyVault()151+                syncFreshStart.reset()149152                 duress.clear()150153                 _state.update { it.copy(loading = false, hasVault = false, duressDestroyed = true) }151154             }

view file

app/src/main/java/com/peachpasswords/ui/settings/PanicScreen.kt+3 −0
@@ -48,6 +48,7 @@4848 import dagger.hilt.android.qualifiers.ApplicationContext4949 import com.peachpasswords.ui.onboarding.ONBOARD_KEY5050 import com.peachpasswords.ui.onboarding.ONBOARD_PREFS51+import com.peachpasswords.sync.SyncFreshStartResetter5152 import com.peachpasswords.vault.VaultRepository5253 import dagger.hilt.android.lifecycle.HiltViewModel5354 import kotlinx.coroutines.launch@@ -56,12 +57,14 @@5657 @HiltViewModel5758 class PanicViewModel @Inject constructor(5859     private val vault: VaultRepository,60+    private val syncFreshStart: SyncFreshStartResetter,5961     @ApplicationContext private val appContext: Context,6062 ) : ViewModel() {6163     val entryCount: Int get() = vault.getEntries().size6264 6365     fun destroy(onDone: () -> Unit) = viewModelScope.launch {6466         vault.destroyVault()67+        syncFreshStart.reset()6568         // Destroying the vault puts the app back to factory state, so clear the6669         // "already onboarded" flag too — otherwise the next launch skipped the6770         // intro and dropped straight onto create-vault, which is not what a

view file

app/src/test/java/com/peachpasswords/sync/SyncFreshStartResetterTest.kt+35 −0
@@ -0,0 +1,35 @@1+package com.peachpasswords.sync2+3+import org.junit.Assert.assertEquals4+import org.junit.Test5+6+class SyncFreshStartResetterTest {7+    @Test fun fresh_start_stops_the_service_then_clears_every_sync_state_store() {8+        val calls = mutableListOf<String>()9+10+        resetSyncStateForFreshStart(11+            stopService = { calls += "service" },12+            clearDevices = { calls += "devices" },13+            clearPendingDevices = { calls += "pending" },14+            clearAlerts = { calls += "alerts" },15+            clearOutcomes = { calls += "outcomes" },16+            clearRelaySettings = { calls += "relay" },17+            clearBackgroundPreference = { calls += "background" },18+            clearLocalIdentity = { calls += "identity" },19+        )20+21+        assertEquals(22+            listOf(23+                "service",24+                "devices",25+                "pending",26+                "alerts",27+                "outcomes",28+                "relay",29+                "background",30+                "identity",31+            ),32+            calls,33+        )34+    }35+}

view file