feat: [#27] sync CalDAV en arrière-plan (WorkManager PeriodicWork, SyncScheduler, reprise au démarrage)

This commit is contained in:
2026-06-06 06:46:28 +02:00
parent d3e9ad4753
commit ee1dac46cb
3 changed files with 93 additions and 2 deletions
@@ -3,14 +3,17 @@ package com.planify.mobile.data.notification
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.planify.mobile.data.sync.SyncScheduler
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
@Inject lateinit var syncScheduler: SyncScheduler
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_BOOT_COMPLETED) return if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
// TODO #14 : replanifier toutes les alarmes depuis la base de données syncScheduler.schedule()
// Inject ReminderScheduler + ReminderRepository et rejouer tous les rappels actifs
} }
} }
@@ -0,0 +1,37 @@
package com.planify.mobile.data.sync
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.planify.mobile.data.caldav.CalDavSyncManager
import com.planify.mobile.data.caldav.SyncResult
import com.planify.mobile.domain.repository.SourceRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@HiltWorker
class CalDavSyncWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val sourceRepository: SourceRepository,
private val syncManager: CalDavSyncManager,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val sources = sourceRepository.getCaldavSources()
if (sources.isEmpty()) return Result.success()
var hasError = false
sources.forEach { source ->
val result = syncManager.incrementalSync(source)
if (result is SyncResult.Failure) hasError = true
}
return if (hasError) Result.retry() else Result.success()
}
companion object {
const val WORK_NAME = "caldav_periodic_sync"
}
}
@@ -0,0 +1,51 @@
package com.planify.mobile.data.sync
import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SyncScheduler @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val workManager = WorkManager.getInstance(context)
fun schedule(intervalMinutes: Long = 30) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = PeriodicWorkRequestBuilder<CalDavSyncWorker>(
repeatInterval = intervalMinutes,
repeatIntervalTimeUnit = TimeUnit.MINUTES,
)
.setConstraints(constraints)
.build()
workManager.enqueueUniquePeriodicWork(
CalDavSyncWorker.WORK_NAME,
ExistingPeriodicWorkPolicy.UPDATE,
request,
)
}
fun cancel() {
workManager.cancelUniqueWork(CalDavSyncWorker.WORK_NAME)
}
fun syncNow() {
val request = androidx.work.OneTimeWorkRequestBuilder<CalDavSyncWorker>()
.setConstraints(
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
)
.build()
workManager.enqueue(request)
}
}