mirror of
https://github.com/vide/matedroid.git
synced 2026-01-20 00:03:17 +08:00
feat(sync): add periodic sync and pull-to-refresh sync in Stats screen
- Pull-to-refresh now triggers a background sync to fetch new data - Automatic sync every 60 seconds while Stats screen is visible - Uses ExistingWorkPolicy.KEEP to avoid interrupting running syncs - Skips sync if already syncing (via SyncManager.syncStatus check) This ensures new drives/charges are synced while the app is running, without requiring the user to close and reopen the app. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- **Stats Sync**: Initial sync now starts automatically on first-time setup instead of requiring manual "Force Full Resync"
|
||||
### Added
|
||||
- **Stats Sync**: Pull-to-refresh in Stats screen now triggers a background sync
|
||||
- **Stats Sync**: Automatic sync every 60 seconds while Stats screen is visible
|
||||
|
||||
## [0.8.0] - 2026-01-05
|
||||
|
||||
|
||||
@@ -103,6 +103,14 @@ fun StatsScreen(
|
||||
viewModel.setCarId(carId)
|
||||
}
|
||||
|
||||
// Periodic sync every 60 seconds while the screen is visible
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
kotlinx.coroutines.delay(60_000L) // Wait 60 seconds
|
||||
viewModel.triggerSync()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.error) {
|
||||
uiState.error?.let { error ->
|
||||
snackbarHostState.showSnackbar(error)
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package com.matedroid.ui.screens.stats
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
import com.matedroid.data.repository.StatsRepository
|
||||
import com.matedroid.data.sync.DataSyncWorker
|
||||
import com.matedroid.data.sync.SyncLogCollector
|
||||
import com.matedroid.data.sync.SyncManager
|
||||
import com.matedroid.domain.model.CarStats
|
||||
@@ -10,8 +18,8 @@ import com.matedroid.domain.model.SyncPhase
|
||||
import com.matedroid.domain.model.SyncProgress
|
||||
import com.matedroid.domain.model.YearFilter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -33,6 +41,7 @@ data class StatsUiState(
|
||||
|
||||
@HiltViewModel
|
||||
class StatsViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val statsRepository: StatsRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val syncLogCollector: SyncLogCollector
|
||||
@@ -44,6 +53,9 @@ class StatsViewModel @Inject constructor(
|
||||
/** Sync logs for debug viewing */
|
||||
val syncLogs: StateFlow<List<String>> = syncLogCollector.logs
|
||||
|
||||
/** Expose sync status for UI to observe */
|
||||
val syncStatus = syncManager.syncStatus
|
||||
|
||||
private var carId: Int? = null
|
||||
private var syncObserverJob: Job? = null
|
||||
|
||||
@@ -88,11 +100,40 @@ class StatsViewModel @Inject constructor(
|
||||
fun refresh() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(isRefreshing = true) }
|
||||
// Trigger sync to fetch new data from server
|
||||
triggerSync()
|
||||
loadStatsInternal()
|
||||
_uiState.update { it.copy(isRefreshing = false) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a background sync to fetch new data from the server.
|
||||
* Uses KEEP policy to avoid interrupting a running sync.
|
||||
*/
|
||||
fun triggerSync() {
|
||||
// Skip if sync is already running
|
||||
if (syncManager.syncStatus.value.isAnySyncing) {
|
||||
return
|
||||
}
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val syncRequest = OneTimeWorkRequestBuilder<DataSyncWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.addTag(DataSyncWorker.TAG)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
DataSyncWorker.WORK_NAME,
|
||||
ExistingWorkPolicy.KEEP, // Don't interrupt running sync
|
||||
syncRequest
|
||||
)
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
_uiState.update { it.copy(error = null) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user