Initial commit — SyncFlow Android file sync app

Supports WebDAV, SFTP, SFTPGo, Nextcloud, ownCloud, Google Drive,
Dropbox, and OneDrive. Credentials encrypted with Android Keystore.
Biometric app-lock, conflict resolution, and auto-sync via WorkManager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 20:21:20 +00:00
commit cff4233de6
95 changed files with 5381 additions and 0 deletions
@@ -0,0 +1,47 @@
package com.syncflow.ui.pairdetail
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.worker.SyncWorker
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
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)
fun syncNow() {
val p = pair.value ?: return
workManager.enqueue(SyncWorker.buildOneTimeRequest(p.id, p.wifiOnly, p.chargingOnly))
}
fun delete() {
viewModelScope.launch {
pair.value?.let { syncPairDao.delete(it) }
}
}
}