f751b26a9e
- Add FileWatchService for real-time ON_CHANGE sync (FileObserver for direct paths, ContentObserver for SAF content:// URIs), 5s debounce - Fix remote browser stuck spinner: cancel in-flight jobs on navigation, reset entries immediately, add Retry button on error - Fix browser reuse bug: LaunchedEffect key now includes initialPath - Fix WebDavProvider: rethrow XML parse errors (no more silent Empty folder) and URL-decode file names from href - Notifications now use BigTextStyle showing per-file-type counts (Uploaded/Downloaded/Deleted) matching Autosync notification style - Wire FileWatchService into BootReceiver and HomeViewModel toggle - Register FileWatchService in AndroidManifest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
2.4 KiB
Kotlin
63 lines
2.4 KiB
Kotlin
package com.syncflow.ui.home
|
|
|
|
import android.content.Context
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import androidx.work.ExistingPeriodicWorkPolicy
|
|
import androidx.work.WorkManager
|
|
import com.syncflow.data.db.SyncPairDao
|
|
import com.syncflow.data.db.entities.SyncPairEntity
|
|
import com.syncflow.domain.model.ScheduleType
|
|
import com.syncflow.worker.FileWatchService
|
|
import com.syncflow.worker.SyncWorker
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
import kotlinx.coroutines.flow.SharingStarted
|
|
import kotlinx.coroutines.flow.stateIn
|
|
import kotlinx.coroutines.launch
|
|
import javax.inject.Inject
|
|
|
|
@HiltViewModel
|
|
class HomeViewModel @Inject constructor(
|
|
private val syncPairDao: SyncPairDao,
|
|
private val workManager: WorkManager,
|
|
@ApplicationContext private val context: Context,
|
|
) : ViewModel() {
|
|
|
|
val syncPairs = syncPairDao.observeAll()
|
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
|
|
|
fun triggerSync(pair: SyncPairEntity) {
|
|
val req = SyncWorker.buildOneTimeRequest(pair.id, wifiOnly = false, chargingOnly = false)
|
|
workManager.enqueue(req)
|
|
}
|
|
|
|
fun toggleEnabled(pair: SyncPairEntity) {
|
|
viewModelScope.launch {
|
|
val nowEnabled = !pair.isEnabled
|
|
syncPairDao.update(pair.copy(isEnabled = nowEnabled))
|
|
if (nowEnabled) {
|
|
when (pair.scheduleType) {
|
|
ScheduleType.ON_CHANGE -> FileWatchService.start(context)
|
|
ScheduleType.MANUAL -> { /* nothing */ }
|
|
else -> {
|
|
val req = SyncWorker.buildPeriodicRequest(
|
|
pair.id,
|
|
pair.scheduleIntervalMinutes.toLong().coerceAtLeast(15),
|
|
pair.wifiOnly,
|
|
pair.chargingOnly,
|
|
)
|
|
workManager.enqueueUniquePeriodicWork("periodic_${pair.id}", ExistingPeriodicWorkPolicy.UPDATE, req)
|
|
}
|
|
}
|
|
} else {
|
|
workManager.cancelAllWorkByTag("sync_${pair.id}")
|
|
// Refresh watcher (it will stop itself if no ON_CHANGE pairs remain)
|
|
if (pair.scheduleType == ScheduleType.ON_CHANGE) {
|
|
FileWatchService.start(context)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|