c60eb8d27b
Build & Release APK / build (push) Has been cancelled
- SyncEngine: accepts onProgress callback — emits uploaded/downloaded/ deleted/bytes counts atomically as each file completes - SyncWorker: streams progress to WorkManager data so the UI can poll it live; reports per-run counters in the completion notification; adds pause/resume support - HomeViewModel/PairDetailViewModel: subscribe to live WorkManager progress and surface it via SyncProgress state - SyncPairEntity/SyncPairDao/SyncDatabase: persist last-run counters (uploaded, downloaded, deleted, bytesTransferred) in the DB with a Room migration (v3→v4) - AppModule: provides WorkManager as an injectable singleton - .gitignore: add .kotlin/ to exclude compiler session files Security: no new issues — all logging via Timber (debug-only), DB queries use Room parameterized API, file sharing via FileProvider. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
71 lines
2.6 KiB
Kotlin
71 lines
2.6 KiB
Kotlin
package com.syncflow.ui.pairdetail
|
|
|
|
import androidx.lifecycle.SavedStateHandle
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import androidx.work.WorkInfo
|
|
import androidx.work.WorkManager
|
|
import com.syncflow.data.db.SyncConflictDao
|
|
import com.syncflow.data.db.SyncEventDao
|
|
import com.syncflow.data.db.SyncPairDao
|
|
import com.syncflow.domain.model.SyncStatus
|
|
import com.syncflow.worker.SyncWorker
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
import com.syncflow.ui.shared.SyncProgress
|
|
import kotlinx.coroutines.flow.SharingStarted
|
|
import kotlinx.coroutines.flow.map
|
|
import kotlinx.coroutines.flow.stateIn
|
|
import kotlinx.coroutines.launch
|
|
import javax.inject.Inject
|
|
|
|
@HiltViewModel
|
|
class PairDetailViewModel @Inject constructor(
|
|
private val syncPairDao: SyncPairDao,
|
|
private val eventDao: SyncEventDao,
|
|
private val conflictDao: SyncConflictDao,
|
|
private val workManager: WorkManager,
|
|
savedState: SavedStateHandle,
|
|
) : ViewModel() {
|
|
|
|
private val pairId = savedState.get<Long>("pairId")!!
|
|
|
|
val pair = syncPairDao.observeById(pairId)
|
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
|
|
|
val events = eventDao.observeRecent(pairId)
|
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
|
|
|
val unresolvedConflicts = conflictDao.observeUnresolvedCount(pairId)
|
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0)
|
|
|
|
val syncProgress = workManager.getWorkInfosByTagFlow("sync_$pairId")
|
|
.map { infos ->
|
|
infos.firstOrNull { it.state == WorkInfo.State.RUNNING }?.progress?.let { data ->
|
|
SyncProgress(
|
|
uploaded = data.getInt(SyncWorker.KEY_PROGRESS_UPLOADED, 0),
|
|
downloaded = data.getInt(SyncWorker.KEY_PROGRESS_DOWNLOADED, 0),
|
|
deleted = data.getInt(SyncWorker.KEY_PROGRESS_DELETED, 0),
|
|
bytesTransferred = data.getLong(SyncWorker.KEY_PROGRESS_BYTES, 0L),
|
|
).takeIf { it.uploaded > 0 || it.downloaded > 0 || it.deleted > 0 }
|
|
}
|
|
}
|
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
|
|
|
fun syncNow() {
|
|
val p = pair.value ?: return
|
|
workManager.enqueue(SyncWorker.buildOneTimeRequest(p.id, wifiOnly = false, chargingOnly = false))
|
|
}
|
|
|
|
fun pauseSync() {
|
|
val p = pair.value ?: return
|
|
workManager.cancelAllWorkByTag("sync_${p.id}")
|
|
viewModelScope.launch { syncPairDao.updateStatus(p.id, SyncStatus.PAUSED) }
|
|
}
|
|
|
|
fun delete() {
|
|
viewModelScope.launch {
|
|
pair.value?.let { syncPairDao.delete(it) }
|
|
}
|
|
}
|
|
}
|