cff4233de6
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>
76 lines
2.8 KiB
Kotlin
76 lines
2.8 KiB
Kotlin
package com.syncflow.ui.browser
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import com.syncflow.data.providers.ProviderFactory
|
|
import com.syncflow.data.repository.AccountRepository
|
|
import com.syncflow.domain.model.RemoteFile
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.flow.update
|
|
import kotlinx.coroutines.launch
|
|
import javax.inject.Inject
|
|
|
|
data class BrowserState(
|
|
val accountId: Long = -1L,
|
|
val currentPath: String = "/",
|
|
val pathStack: List<String> = listOf("/"),
|
|
val entries: List<RemoteFile> = emptyList(),
|
|
val isLoading: Boolean = false,
|
|
val error: String? = null,
|
|
)
|
|
|
|
@HiltViewModel
|
|
class RemoteBrowserViewModel @Inject constructor(
|
|
private val accountRepository: AccountRepository,
|
|
private val providerFactory: ProviderFactory,
|
|
) : ViewModel() {
|
|
|
|
private val _state = MutableStateFlow(BrowserState())
|
|
val state = _state.asStateFlow()
|
|
|
|
fun init(accountId: Long, startPath: String = "/") {
|
|
_state.update { it.copy(accountId = accountId, currentPath = startPath, pathStack = listOf(startPath)) }
|
|
loadPath(accountId, startPath)
|
|
}
|
|
|
|
fun navigateTo(path: String) {
|
|
val accountId = _state.value.accountId
|
|
_state.update { s -> s.copy(currentPath = path, pathStack = s.pathStack + path) }
|
|
loadPath(accountId, path)
|
|
}
|
|
|
|
fun navigateUp(): Boolean {
|
|
val stack = _state.value.pathStack
|
|
if (stack.size <= 1) return false
|
|
val newStack = stack.dropLast(1)
|
|
val parent = newStack.last()
|
|
_state.update { it.copy(currentPath = parent, pathStack = newStack) }
|
|
loadPath(_state.value.accountId, parent)
|
|
return true
|
|
}
|
|
|
|
private fun loadPath(accountId: Long, path: String) {
|
|
viewModelScope.launch {
|
|
_state.update { it.copy(isLoading = true, error = null) }
|
|
val account = accountRepository.getAccount(accountId)
|
|
if (account == null) {
|
|
_state.update { it.copy(isLoading = false, error = "Account not found") }
|
|
return@launch
|
|
}
|
|
val provider = runCatching { providerFactory.create(account) }.getOrElse { e ->
|
|
_state.update { it.copy(isLoading = false, error = e.message) }
|
|
return@launch
|
|
}
|
|
provider.listFiles(path)
|
|
.onSuccess { files ->
|
|
_state.update { it.copy(isLoading = false, entries = files.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))) }
|
|
}
|
|
.onFailure { e ->
|
|
_state.update { it.copy(isLoading = false, error = e.message ?: "Failed to list files") }
|
|
}
|
|
}
|
|
}
|
|
}
|