@@ -11,9 +11,8 @@ import com.flipcash.app.contacts.device.PickedContactData
1111import com.flipcash.app.contacts.device.ScopeAwareContactReader
1212import com.flipcash.app.phone.PhoneUtils
1313import com.flipcash.app.contacts.sync.ContactChecksum
14- import com.flipcash.app.persistence.FlipcashDatabase
1514import com.flipcash.app.persistence.entities.ContactMappingEntity
16- import com.flipcash.app.persistence.entities.ContactSyncStateEntity
15+ import com.flipcash.app.persistence.sources.ContactDataSource
1716import com.flipcash.services.controllers.ContactListController
1817import com.flipcash.services.controllers.ResolverController
1918import com.flipcash.services.models.CheckSyncError
@@ -54,6 +53,7 @@ class ContactCoordinator @Inject constructor(
5453 private val networkObserver : NetworkConnectivityListener ,
5554 private val contactReader : ScopeAwareContactReader ,
5655 private val phoneUtils : PhoneUtils ,
56+ private val contactDataSource : ContactDataSource ,
5757) : SessionListener, DefaultLifecycleObserver {
5858
5959 companion object {
@@ -134,8 +134,7 @@ class ContactCoordinator @Inject constructor(
134134
135135 suspend fun removeContact (e164 : String ) {
136136 contactReader.removeSelectedContact(e164)
137- val db = FlipcashDatabase .getInstance() ? : return
138- db.contactDao().deleteMappings(listOf (e164))
137+ contactDataSource.deleteMappings(listOf (e164))
139138 _state .update { state ->
140139 state.copy(
141140 contacts = state.contacts - e164,
@@ -153,8 +152,7 @@ class ContactCoordinator @Inject constructor(
153152 _state .value = ContactState ()
154153 cluster.value = null
155154 contactReader.reset()
156- val db = FlipcashDatabase .getInstance() ? : return
157- db.contactDao().clearAll()
155+ contactDataSource.clear()
158156 trace(tag = TAG , message = " reset complete" , type = TraceType .Process )
159157 }
160158
@@ -166,16 +164,15 @@ class ContactCoordinator @Inject constructor(
166164 * the next foreground retries.
167165 */
168166 suspend fun clearServerContactSetIfRevoked () {
169- val db = FlipcashDatabase .getInstance() ? : return
170- val syncState = db.contactDao().getSyncState() ? : return
167+ val syncState = contactDataSource.getSyncState() ? : return
171168 if (syncState.checksumBytes.all { it == 0 .toByte() }) return
172169
173170 if (! contactReader.isPermissionRevoked()) return
174171
175172 clearServerContactSet()
176173 _state .value = ContactState ()
177174 contactReader.reset()
178- db.contactDao().clearAll ()
175+ contactDataSource.clear ()
179176 trace(tag = TAG , message = " Cleared server contact set after permission revoke" , type = TraceType .Process )
180177 }
181178
@@ -201,9 +198,8 @@ class ContactCoordinator @Inject constructor(
201198 // region Internal
202199
203200 private suspend fun hydrateFromPersistence () {
204- val db = FlipcashDatabase .getInstance() ? : return
205- val syncState = db.contactDao().getSyncState()
206- val mappings = db.contactDao().getAllMappings()
201+ val syncState = contactDataSource.getSyncState()
202+ val mappings = contactDataSource.get()
207203
208204 val hasEverSynced = syncState != null || mappings.isNotEmpty()
209205 if (mappings.isEmpty()) {
@@ -253,12 +249,7 @@ class ContactCoordinator @Inject constructor(
253249 val newChecksum = ContactChecksum .compute(deviceContacts.keys)
254250
255251 // 3. Diff against persisted mappings
256- val db = FlipcashDatabase .getInstance() ? : run {
257- _state .update { it.copy(syncState = SyncState .Error ) }
258- return Result .failure(IllegalStateException (" Database unavailable" ))
259- }
260- val dao = db.contactDao()
261- val existingMappings = dao.getAllMappings()
252+ val existingMappings = contactDataSource.get()
262253 val existingE164s = existingMappings.map { it.e164 }.toSet()
263254 val newE164s = deviceContacts.keys
264255
@@ -275,9 +266,9 @@ class ContactCoordinator @Inject constructor(
275266 displayNumber = phoneUtils.formatNumber(contact.e164),
276267 )
277268 }
278- dao.upsertMappings (allEntities)
269+ contactDataSource.upsert (allEntities)
279270 if (removes.isNotEmpty()) {
280- dao .deleteMappings(removes.toList())
271+ contactDataSource .deleteMappings(removes.toList())
281272 }
282273
283274 // Update in-memory contacts with displayNumber, merging into existing state
@@ -288,7 +279,7 @@ class ContactCoordinator @Inject constructor(
288279 _state .update { it.copy(contacts = it.contacts + enrichedContacts) }
289280
290281 // 5. CheckSync with server
291- val syncState = dao .getSyncState()
282+ val syncState = contactDataSource .getSyncState()
292283 val oldChecksum = syncState?.let { Checksum (it.checksumBytes.toList()) }
293284
294285 val checkSyncResult = contactListController.checkSync(newChecksum)
@@ -297,7 +288,7 @@ class ContactCoordinator @Inject constructor(
297288 onSuccess = { serverChecksum ->
298289 // Checksums match — skip upload
299290 trace(tag = TAG , message = " Contacts in sync with server" , type = TraceType .Process )
300- persistSyncState(dao, newChecksum)
291+ contactDataSource. persistSyncState(newChecksum)
301292 },
302293 onFailure = { error ->
303294 when (error) {
@@ -313,30 +304,30 @@ class ContactCoordinator @Inject constructor(
313304 deltaResult.fold(
314305 onSuccess = {
315306 trace(tag = TAG , message = " Delta upload successful" , type = TraceType .Process )
316- persistSyncState(dao, newChecksum)
307+ contactDataSource. persistSyncState(newChecksum)
317308 },
318309 onFailure = { deltaError ->
319310 if (deltaError is DeltaUploadError .ChecksumDrift || deltaError is DeltaUploadError .ChecksumMismatch ) {
320- performFullUpload(newE164s, newChecksum, dao )
311+ performFullUpload(newE164s, newChecksum)
321312 } else {
322313 trace(tag = TAG , message = " Delta upload failed: ${deltaError.message} " , type = TraceType .Error )
323314 }
324315 }
325316 )
326317 } else {
327- performFullUpload(newE164s, newChecksum, dao )
318+ performFullUpload(newE164s, newChecksum)
328319 }
329320 }
330321 else -> {
331322 // First sync or other error — full upload
332- performFullUpload(newE164s, newChecksum, dao )
323+ performFullUpload(newE164s, newChecksum)
333324 }
334325 }
335326 }
336327 )
337328
338329 // 6. GetFlipcashContacts
339- fetchFlipcashContacts(newChecksum, dao )
330+ fetchFlipcashContacts(newChecksum)
340331
341332 _state .update { it.copy(syncState = SyncState .Synced , hasEverSynced = true ) }
342333 trace(tag = TAG , message = " Contact sync complete" , type = TraceType .Process )
@@ -352,7 +343,6 @@ class ContactCoordinator @Inject constructor(
352343 private suspend fun performFullUpload (
353344 e164s : Set <String >,
354345 checksum : Checksum ,
355- dao : com.flipcash.app.persistence.dao.ContactDao ,
356346 ) {
357347 val phones = e164s.map { ContactMethod .Phone (it) }
358348 val chunked = phones.chunked(500 )
@@ -365,45 +355,30 @@ class ContactCoordinator @Inject constructor(
365355 result.fold(
366356 onSuccess = {
367357 trace(tag = TAG , message = " Full upload successful (${e164s.size} contacts)" , type = TraceType .Process )
368- persistSyncState(dao, checksum)
358+ contactDataSource. persistSyncState(checksum)
369359 },
370360 onFailure = { error ->
371361 trace(tag = TAG , message = " Full upload failed: ${error.message} " , type = TraceType .Error )
372362 }
373363 )
374364 }
375365
376- private suspend fun persistSyncState (
377- dao : com.flipcash.app.persistence.dao.ContactDao ,
378- checksum : Checksum ,
379- ) {
380- dao.upsertSyncState(
381- ContactSyncStateEntity (
382- checksumBytes = checksum.byteArray,
383- lastSyncTimestamp = System .currentTimeMillis(),
384- )
385- )
386- }
387-
388- private suspend fun fetchFlipcashContacts (
389- checksum : Checksum ,
390- dao : com.flipcash.app.persistence.dao.ContactDao ,
391- ) {
366+ private suspend fun fetchFlipcashContacts (checksum : Checksum ) {
392367 try {
393368 val result = contactListController.getFlipcashContacts(checksum)
394369 .firstOrNull()
395370
396371 result?.onSuccess { phones ->
397372 val flipcashE164s = phones.map { it.phoneNumber }.toSet()
398- dao .clearFlipcashStatus()
373+ contactDataSource .clearFlipcashStatus()
399374 if (flipcashE164s.isNotEmpty()) {
400- dao .markAsFlipcash(flipcashE164s.toList())
375+ contactDataSource .markAsFlipcash(flipcashE164s.toList())
401376 }
402377 _state .update { it.copy(flipcashE164s = flipcashE164s) }
403378 trace(tag = TAG , message = " Found ${flipcashE164s.size} contacts on Flipcash" , type = TraceType .Process )
404379 }?.onFailure { error ->
405380 if (error is GetContactsError .NotFound ) {
406- dao .clearFlipcashStatus()
381+ contactDataSource .clearFlipcashStatus()
407382 _state .update { it.copy(flipcashE164s = emptySet()) }
408383 trace(tag = TAG , message = " No contacts on Flipcash yet" , type = TraceType .Process )
409384 } else {
0 commit comments