Yandex штуки
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
package com.github.nullptroma.wallenc.data.network.yandexdisk
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class YandexDiskAuthException(message: String? = null) : IOException(message)
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.github.nullptroma.wallenc.data.network.yandexdisk.repository
|
package com.github.nullptroma.wallenc.data.network.yandexdisk.repository
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.YandexDiskApi
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.YandexDiskApi
|
||||||
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.YandexDiskAuthException
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.CustomPropertiesPatchDto
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.CustomPropertiesPatchDto
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.DiskInfoDto
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.DiskInfoDto
|
||||||
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.EmbeddedResourceListDto
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.LinkDto
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.LinkDto
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.OperationStatusDto
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.OperationStatusDto
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.ResourceDto
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.ResourceDto
|
||||||
@@ -22,8 +25,6 @@ import retrofit2.Response
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class YandexDiskAuthException(message: String? = null) : IOException(message)
|
|
||||||
|
|
||||||
class YandexDiskRepository(
|
class YandexDiskRepository(
|
||||||
private val api: YandexDiskApi,
|
private val api: YandexDiskApi,
|
||||||
private val rawHttp: okhttp3.OkHttpClient,
|
private val rawHttp: okhttp3.OkHttpClient,
|
||||||
@@ -36,7 +37,15 @@ class YandexDiskRepository(
|
|||||||
|
|
||||||
suspend fun list(path: String, limit: Int, offset: Int, sort: String? = null): ResourceDto =
|
suspend fun list(path: String, limit: Int, offset: Int, sort: String? = null): ResourceDto =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
wrapAuth { api.listResources(path, limit, offset, sort) }
|
try {
|
||||||
|
wrapAuth { api.listResources(path, limit, offset, sort) }
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
if (e.code() == 404) {
|
||||||
|
ResourceDto(embedded = EmbeddedResourceListDto(items = emptyList()))
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun get(path: String): ResourceDto = withContext(ioDispatcher) {
|
suspend fun get(path: String): ResourceDto = withContext(ioDispatcher) {
|
||||||
@@ -47,7 +56,7 @@ class YandexDiskRepository(
|
|||||||
val resp = wrapAuth { api.createFolder(path) }
|
val resp = wrapAuth { api.createFolder(path) }
|
||||||
when (resp.code()) {
|
when (resp.code()) {
|
||||||
201 -> Unit
|
201 -> Unit
|
||||||
409 -> Unit // уже существует
|
409 -> Unit
|
||||||
else -> throw failure("createFolder", resp)
|
else -> throw failure("createFolder", resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,8 +179,17 @@ class YandexDiskRepository(
|
|||||||
try {
|
try {
|
||||||
return block()
|
return block()
|
||||||
} catch (e: HttpException) {
|
} catch (e: HttpException) {
|
||||||
if (e.code() == 401) throw YandexDiskAuthException(e.message())
|
when (e.code()) {
|
||||||
throw e
|
401 -> {
|
||||||
|
Log.w(TAG, "Disk API 401: ${e.message()}")
|
||||||
|
throw YandexDiskAuthException(e.message())
|
||||||
|
}
|
||||||
|
404 -> throw e
|
||||||
|
else -> {
|
||||||
|
Log.w(TAG, "Disk API HTTP ${e.code()}: ${e.message()}")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +202,7 @@ class YandexDiskRepository(
|
|||||||
jackson.readValue(body.string())
|
jackson.readValue(body.string())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "YandexDiskRepo"
|
||||||
private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
|
private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
|
||||||
private val OCTET_STREAM = "application/octet-stream".toMediaType()
|
private val OCTET_STREAM = "application/octet-stream".toMediaType()
|
||||||
private const val OPERATION_POLL_DELAY_MS = 300L
|
private const val OPERATION_POLL_DELAY_MS = 300L
|
||||||
|
|||||||
@@ -86,10 +86,8 @@ abstract class BaseStorage(
|
|||||||
|
|
||||||
private suspend fun updateMetaInfo(meta: IStorageMetaInfo) = withContext(ioDispatcher) {
|
private suspend fun updateMetaInfo(meta: IStorageMetaInfo) = withContext(ioDispatcher) {
|
||||||
val writer = accessor.openWriteSystemFile(metaInfoFileName)
|
val writer = accessor.openWriteSystemFile(metaInfoFileName)
|
||||||
try {
|
writer.use { writer ->
|
||||||
jackson.writeValue(writer, meta)
|
jackson.writeValue(writer, meta)
|
||||||
} finally {
|
|
||||||
writer.close()
|
|
||||||
}
|
}
|
||||||
_metaInfo.value = meta
|
_metaInfo.value = meta
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.github.nullptroma.wallenc.data.storages.yandex
|
package com.github.nullptroma.wallenc.data.storages.yandex
|
||||||
|
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.repository.YandexDiskAuthException
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.YandexDiskAuthException
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.ResourceDto
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.dto.ResourceDto
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.repository.YandexDiskRepository
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.repository.YandexDiskRepository
|
||||||
import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosed
|
import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosed
|
||||||
@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import android.util.Log
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@@ -72,13 +73,16 @@ class YandexStorageAccessor(
|
|||||||
try {
|
try {
|
||||||
scanSizeAndNumOfFiles()
|
scanSizeAndNumOfFiles()
|
||||||
_storageReady.value = true
|
_storageReady.value = true
|
||||||
|
Log.d(TAG, "init ok storageUuid=$storageUuid")
|
||||||
} catch (e: YandexDiskAuthException) {
|
} catch (e: YandexDiskAuthException) {
|
||||||
reportAuthFailure()
|
reportAuthFailure()
|
||||||
_storageReady.value = false
|
_storageReady.value = false
|
||||||
|
Log.w(TAG, "init auth failed storageUuid=$storageUuid", e)
|
||||||
throw e
|
throw e
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
_storageReady.value = false
|
_storageReady.value = false
|
||||||
throw Exception("Yandex storage init failed")
|
Log.w(TAG, "init failed storageUuid=$storageUuid", e)
|
||||||
|
throw Exception("Yandex storage init failed", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,6 +436,7 @@ class YandexStorageAccessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "YandexStorageAcc"
|
||||||
private const val SYSTEM_HIDDEN_DIRNAME = "wallenc-yandex-system"
|
private const val SYSTEM_HIDDEN_DIRNAME = "wallenc-yandex-system"
|
||||||
private const val DATA_PAGE_LENGTH = 10
|
private const val DATA_PAGE_LENGTH = 10
|
||||||
private const val API_LIST_LIMIT = 200
|
private const val API_LIST_LIMIT = 200
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.github.nullptroma.wallenc.data.vaults.yandex
|
package com.github.nullptroma.wallenc.data.vaults.yandex
|
||||||
|
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.repository.YandexDiskAuthException
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.YandexDiskAuthException
|
||||||
import com.github.nullptroma.wallenc.data.network.yandexdisk.repository.YandexDiskRepository
|
import com.github.nullptroma.wallenc.data.network.yandexdisk.repository.YandexDiskRepository
|
||||||
import com.github.nullptroma.wallenc.data.storages.yandex.YandexStorage
|
import com.github.nullptroma.wallenc.data.storages.yandex.YandexStorage
|
||||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
|
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
|
||||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.CoroutineDispatcher
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import android.util.Log
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -64,12 +65,15 @@ class YandexVault(
|
|||||||
_availableSpace.value = (total - used).coerceAtLeast(0L)
|
_availableSpace.value = (total - used).coerceAtLeast(0L)
|
||||||
_vaultReachable.value = true
|
_vaultReachable.value = true
|
||||||
_storages.value = loadStoragesList()
|
_storages.value = loadStoragesList()
|
||||||
} catch (_: YandexDiskAuthException) {
|
Log.d(TAG, "refresh ok uuid=$uuid storages=${_storages.value.size}")
|
||||||
|
} catch (e: YandexDiskAuthException) {
|
||||||
_vaultReachable.value = false
|
_vaultReachable.value = false
|
||||||
_storages.value = emptyList()
|
_storages.value = emptyList()
|
||||||
} catch (_: Exception) {
|
Log.w(TAG, "refresh auth failed uuid=$uuid: ${e.message}")
|
||||||
|
} catch (e: Exception) {
|
||||||
_vaultReachable.value = false
|
_vaultReachable.value = false
|
||||||
_storages.value = emptyList()
|
_storages.value = emptyList()
|
||||||
|
Log.w(TAG, "refresh failed uuid=$uuid", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +100,8 @@ class YandexVault(
|
|||||||
try {
|
try {
|
||||||
storage.init()
|
storage.init()
|
||||||
out.add(storage)
|
out.add(storage)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
// пропускаем битое/частично созданное хранилище
|
Log.w(TAG, "skip broken storage uuid=$storageUuid: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (items.size < APP_LIST_LIMIT) break
|
if (items.size < APP_LIST_LIMIT) break
|
||||||
@@ -108,6 +112,7 @@ class YandexVault(
|
|||||||
|
|
||||||
override suspend fun createStorage(): IStorage = withContext(ioDispatcher) {
|
override suspend fun createStorage(): IStorage = withContext(ioDispatcher) {
|
||||||
val id = UUID.randomUUID()
|
val id = UUID.randomUUID()
|
||||||
|
Log.d(TAG, "createStorage start vault=$uuid storage=$id")
|
||||||
repo.createFolder("app:/$id")
|
repo.createFolder("app:/$id")
|
||||||
val storage = YandexStorage(
|
val storage = YandexStorage(
|
||||||
uuid = id,
|
uuid = id,
|
||||||
@@ -121,6 +126,7 @@ class YandexVault(
|
|||||||
)
|
)
|
||||||
storage.init()
|
storage.init()
|
||||||
_storages.value = _storages.value + storage
|
_storages.value = _storages.value + storage
|
||||||
|
Log.d(TAG, "createStorage done storage=$id")
|
||||||
storage
|
storage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,11 +138,13 @@ class YandexVault(
|
|||||||
|
|
||||||
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
|
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
|
||||||
if (storage !is YandexStorage) return@withContext
|
if (storage !is YandexStorage) return@withContext
|
||||||
|
Log.d(TAG, "remove storage=${storage.uuid}")
|
||||||
repo.delete("app:/${storage.uuid}", permanently = true)
|
repo.delete("app:/${storage.uuid}", permanently = true)
|
||||||
_storages.value = _storages.value.filter { it.uuid != storage.uuid }
|
_storages.value = _storages.value.filter { it.uuid != storage.uuid }
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
private const val TAG = "YandexVault"
|
||||||
private const val APP_LIST_LIMIT = 200
|
private const val APP_LIST_LIMIT = 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
|
|||||||
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
||||||
|
|
||||||
# Retrofit
|
# Retrofit
|
||||||
retrofit = { group="com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"}
|
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
retrofit-converter-scalars = { group="com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit"}
|
retrofit-converter-scalars = { group = "com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit" }
|
||||||
retrofit-converter-jackson = { group="com.squareup.retrofit2", name = "converter-jackson", version.ref = "retrofit"}
|
retrofit-converter-jackson = { group = "com.squareup.retrofit2", name = "converter-jackson", version.ref = "retrofit" }
|
||||||
|
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
@@ -32,7 +33,7 @@ import kotlin.system.measureTimeMillis
|
|||||||
*/
|
*/
|
||||||
abstract class AbstractVaultBrowserViewModel(
|
abstract class AbstractVaultBrowserViewModel(
|
||||||
storagesFlow: Flow<List<IStorage>>,
|
storagesFlow: Flow<List<IStorage>>,
|
||||||
private val canAddStorage: Boolean,
|
private val vaultAvailabilityFlow: Flow<Boolean>,
|
||||||
private val resolveCreateVaultUuid: () -> UUID?,
|
private val resolveCreateVaultUuid: () -> UUID?,
|
||||||
private val removeStorageUseCase: RemoveStorageUseCase,
|
private val removeStorageUseCase: RemoveStorageUseCase,
|
||||||
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
|
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
|
||||||
@@ -43,7 +44,7 @@ abstract class AbstractVaultBrowserViewModel(
|
|||||||
private val taskOrchestrator: ITaskOrchestrator,
|
private val taskOrchestrator: ITaskOrchestrator,
|
||||||
private val logger: ILogger,
|
private val logger: ILogger,
|
||||||
) : ViewModelBase<VaultBrowserScreenState>(
|
) : ViewModelBase<VaultBrowserScreenState>(
|
||||||
VaultBrowserScreenState(storagesList = emptyList(), isLoading = true, canAddStorage = canAddStorage),
|
VaultBrowserScreenState(storagesList = emptyList(), isLoading = true, addStorageFabEnabled = false),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val _messages = MutableSharedFlow<String>()
|
private val _messages = MutableSharedFlow<String>()
|
||||||
@@ -63,6 +64,14 @@ abstract class AbstractVaultBrowserViewModel(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
collectFlows(storagesFlow)
|
collectFlows(storagesFlow)
|
||||||
|
viewModelScope.launch {
|
||||||
|
vaultAvailabilityFlow
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { available ->
|
||||||
|
updateState(state.value.copy(addStorageFabEnabled = available))
|
||||||
|
logger.debug(TAG, "vault availability → add FAB enabled=$available")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateStateLoading() {
|
private fun updateStateLoading() {
|
||||||
@@ -125,20 +134,36 @@ abstract class AbstractVaultBrowserViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createStorage() {
|
fun createStorage() {
|
||||||
if (!state.value.canAddStorage) return
|
if (!state.value.addStorageFabEnabled) {
|
||||||
|
logger.debug(TAG, "createStorage ignored (vault unavailable or FAB disabled)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.debug(TAG, "createStorage: enqueue task")
|
||||||
taskOrchestrator.enqueue(
|
taskOrchestrator.enqueue(
|
||||||
title = "Create storage",
|
title = "Create storage",
|
||||||
dispatcher = Dispatchers.IO,
|
dispatcher = Dispatchers.IO,
|
||||||
work = { ctx ->
|
work = { ctx ->
|
||||||
ctx.log(TaskLogLevel.Info, "Creating storage…")
|
try {
|
||||||
val uuid = resolveCreateVaultUuid()
|
ctx.log(TaskLogLevel.Info, "Creating storage…")
|
||||||
?: throw IllegalStateException("Vault is not available")
|
val uuid = resolveCreateVaultUuid()
|
||||||
manageVaultUseCase.createStorage(uuid)
|
?: throw IllegalStateException("Vault is not available")
|
||||||
ctx.log(TaskLogLevel.Info, "Storage created")
|
logger.debug(TAG, "createStorage: vaultUuid=$uuid")
|
||||||
|
val storage = manageVaultUseCase.createStorage(uuid)
|
||||||
|
ctx.log(TaskLogLevel.Info, "Storage created")
|
||||||
|
logger.debug(TAG, "createStorage: done storageUuid=${storage.uuid}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.debug(TAG, "createStorage failed: ${e.stackTraceToString()}")
|
||||||
|
ctx.log(TaskLogLevel.Error, e.message ?: e.toString())
|
||||||
|
throw e
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val TAG = "VaultBrowser"
|
||||||
|
}
|
||||||
|
|
||||||
private val storageOpMutex = Any()
|
private val storageOpMutex = Any()
|
||||||
private val runningStorages = mutableSetOf<UUID>()
|
private val runningStorages = mutableSetOf<UUID>()
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ class LocalVaultViewModel @Inject constructor(
|
|||||||
storagesFlow = vaultsManager.vaults
|
storagesFlow = vaultsManager.vaults
|
||||||
.map { vaults -> vaults.described().locals.firstOrNull() }
|
.map { vaults -> vaults.described().locals.firstOrNull() }
|
||||||
.flatMapLatest { v -> v?.storages ?: flowOf(emptyList()) },
|
.flatMapLatest { v -> v?.storages ?: flowOf(emptyList()) },
|
||||||
canAddStorage = true,
|
vaultAvailabilityFlow = vaultsManager.vaults
|
||||||
|
.map { vaults -> vaults.described().locals.firstOrNull() }
|
||||||
|
.flatMapLatest { v -> v?.isAvailable ?: flowOf(false) },
|
||||||
resolveCreateVaultUuid = { vaultsManager.vaults.value.described().locals.firstOrNull()?.uuid },
|
resolveCreateVaultUuid = { vaultsManager.vaults.value.described().locals.firstOrNull()?.uuid },
|
||||||
removeStorageUseCase = removeStorageUseCase,
|
removeStorageUseCase = removeStorageUseCase,
|
||||||
getOpenedStoragesUseCase = getOpenedStoragesUseCase,
|
getOpenedStoragesUseCase = getOpenedStoragesUseCase,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
|
|||||||
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -28,8 +30,9 @@ class RemoteVaultViewModel @Inject constructor(
|
|||||||
logger: ILogger,
|
logger: ILogger,
|
||||||
) : AbstractVaultBrowserViewModel(
|
) : AbstractVaultBrowserViewModel(
|
||||||
storagesFlow = manageVaultUseCase.storagesOf(savedStateHandle.requireVaultUuid()),
|
storagesFlow = manageVaultUseCase.storagesOf(savedStateHandle.requireVaultUuid()),
|
||||||
canAddStorage = false,
|
vaultAvailabilityFlow = manageVaultUseCase.observe(savedStateHandle.requireVaultUuid())
|
||||||
resolveCreateVaultUuid = { null },
|
.flatMapLatest { v -> v?.isAvailable ?: flowOf(false) },
|
||||||
|
resolveCreateVaultUuid = { savedStateHandle.requireVaultUuid() },
|
||||||
removeStorageUseCase = removeStorageUseCase,
|
removeStorageUseCase = removeStorageUseCase,
|
||||||
getOpenedStoragesUseCase = getOpenedStoragesUseCase,
|
getOpenedStoragesUseCase = getOpenedStoragesUseCase,
|
||||||
storageFileManagementUseCase = storageFileManagementUseCase,
|
storageFileManagementUseCase = storageFileManagementUseCase,
|
||||||
|
|||||||
@@ -50,12 +50,21 @@ fun VaultBrowserScreen(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentWindowInsets = WindowInsets(0.dp),
|
contentWindowInsets = WindowInsets(0.dp),
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (uiState.canAddStorage) {
|
val fabEnabled = uiState.addStorageFabEnabled
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = { viewModel.createStorage() },
|
onClick = { if (fabEnabled) viewModel.createStorage() },
|
||||||
) {
|
containerColor = if (fabEnabled) {
|
||||||
Icon(Icons.Filled.Add, contentDescription = null)
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
}
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
},
|
||||||
|
contentColor = if (fabEnabled) {
|
||||||
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.55f)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.Add, contentDescription = null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
|
|||||||
data class VaultBrowserScreenState(
|
data class VaultBrowserScreenState(
|
||||||
val storagesList: List<Tree<IStorageInfo>>,
|
val storagesList: List<Tree<IStorageInfo>>,
|
||||||
val isLoading: Boolean,
|
val isLoading: Boolean,
|
||||||
val canAddStorage: Boolean = false,
|
/** FAB «добавить storage»: активна только когда vault доступен (сеть/API/путь). */
|
||||||
|
val addStorageFabEnabled: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user