Реализован UnlockManager

This commit is contained in:
2025-01-18 21:24:41 +03:00
parent c5c0173391
commit b9e73cf197
25 changed files with 598 additions and 91 deletions

View File

@@ -3,7 +3,7 @@ package com.github.nullptroma.wallenc.app.di.modules.data
import android.content.Context import android.content.Context
import com.github.nullptroma.wallenc.data.db.RoomFactory import com.github.nullptroma.wallenc.data.db.RoomFactory
import com.github.nullptroma.wallenc.data.db.app.IAppDb import com.github.nullptroma.wallenc.data.db.app.IAppDb
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@@ -21,8 +21,8 @@ class RoomModule {
@Provides @Provides
@Singleton @Singleton
fun provideStorageKeyDao(database: IAppDb): StorageKeyDao { fun provideStorageKeyDao(database: IAppDb): StorageKeyMapDao {
return database.storageKeyDao return database.storageKeyMapDao
} }
@Provides @Provides

View File

@@ -2,7 +2,8 @@ package com.github.nullptroma.wallenc.app.di.modules.data
import android.content.Context import android.content.Context
import com.github.nullptroma.wallenc.app.di.modules.app.IoDispatcher import com.github.nullptroma.wallenc.app.di.modules.app.IoDispatcher
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
import com.github.nullptroma.wallenc.data.db.app.repository.StorageKeyMapRepository
import com.github.nullptroma.wallenc.data.vaults.UnlockManager import com.github.nullptroma.wallenc.data.vaults.UnlockManager
import com.github.nullptroma.wallenc.data.vaults.VaultsManager import com.github.nullptroma.wallenc.data.vaults.VaultsManager
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
@@ -25,13 +26,21 @@ class SingletonModule {
return VaultsManager(ioDispatcher, context) return VaultsManager(ioDispatcher, context)
} }
@Provides
@Singleton
fun provideStorageKeyMapRepository(dao: StorageKeyMapDao): StorageKeyMapRepository {
return StorageKeyMapRepository(dao)
}
@Provides @Provides
@Singleton @Singleton
fun provideUnlockManager(@IoDispatcher ioDispatcher: CoroutineDispatcher, fun provideUnlockManager(@IoDispatcher ioDispatcher: CoroutineDispatcher,
dao: StorageKeyDao): IUnlockManager { repo: StorageKeyMapRepository,
vaultsManager: IVaultsManager): IUnlockManager {
return UnlockManager( return UnlockManager(
dao = dao, repo = repo,
ioDispatcher = ioDispatcher ioDispatcher = ioDispatcher,
vaultsManager = vaultsManager
) )
} }
} }

View File

@@ -1,7 +1,9 @@
package com.github.nullptroma.wallenc.app.di.modules.domain package com.github.nullptroma.wallenc.app.di.modules.domain
import com.github.nullptroma.wallenc.data.vaults.UnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.domain.usecases.GetAllRawStoragesUseCase import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
import dagger.Module import dagger.Module
@@ -15,14 +17,14 @@ import javax.inject.Singleton
class UseCasesModule { class UseCasesModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetAllRawStoragesUseCase(vaultsManager: IVaultsManager): GetAllRawStoragesUseCase { fun provideGetOpenedStoragesUseCase(unlockManager: IUnlockManager): GetOpenedStoragesUseCase {
return GetAllRawStoragesUseCase(vaultsManager) return GetOpenedStoragesUseCase(unlockManager)
} }
@Provides @Provides
@Singleton @Singleton
fun provideManageLocalVaultUseCase(vaultsManager: IVaultsManager): ManageLocalVaultUseCase { fun provideManageLocalVaultUseCase(vaultsManager: IVaultsManager, unlockManager: IUnlockManager): ManageLocalVaultUseCase {
return ManageLocalVaultUseCase(vaultsManager) return ManageLocalVaultUseCase(vaultsManager, unlockManager)
} }
@Provides @Provides

View File

@@ -2,14 +2,14 @@ package com.github.nullptroma.wallenc.data.db.app
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKey import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKeyMap
interface IAppDb { interface IAppDb {
val storageKeyDao: StorageKeyDao val storageKeyMapDao: StorageKeyMapDao
} }
@Database(entities = [DbStorageKey::class], version = 1, exportSchema = false) @Database(entities = [DbStorageKeyMap::class], version = 2, exportSchema = false)
abstract class AppDb : IAppDb, RoomDatabase() { abstract class AppDb : IAppDb, RoomDatabase() {
abstract override val storageKeyDao: StorageKeyDao abstract override val storageKeyMapDao: StorageKeyMapDao
} }

View File

@@ -1,9 +0,0 @@
package com.github.nullptroma.wallenc.data.db.app.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
@Dao
interface StorageKeyDao {
}

View File

@@ -0,0 +1,22 @@
package com.github.nullptroma.wallenc.data.db.app.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKeyMap
import kotlinx.coroutines.flow.StateFlow
@Dao
interface StorageKeyMapDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(keymap: DbStorageKeyMap)
@Query("SELECT * FROM storage_key_maps")
fun getAll(): List<DbStorageKeyMap>
@Delete
fun delete(keymap: DbStorageKeyMap)
}

View File

@@ -1,11 +0,0 @@
package com.github.nullptroma.wallenc.data.db.app.model
import androidx.room.ColumnInfo
import androidx.room.Entity
@Entity(tableName = "storage_keys", primaryKeys = [ "source_uuid", "dest_uuid" ])
data class DbStorageKey(
@ColumnInfo(name = "source_uuid") val sourceUuid: String,
@ColumnInfo(name = "dest_uuid") val destUuid: String,
@ColumnInfo(name = "key") val key: String
)

View File

@@ -0,0 +1,53 @@
package com.github.nullptroma.wallenc.data.db.app.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.github.nullptroma.wallenc.data.db.app.repository.StorageKeyMapRepository
import com.github.nullptroma.wallenc.data.model.StorageKeyMap
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import java.util.UUID
@Entity(tableName = "storage_key_maps", primaryKeys = [ "source_uuid", "dest_uuid" ])
data class DbStorageKeyMap(
@ColumnInfo(name = "source_uuid") val sourceUuid: String,
@ColumnInfo(name = "dest_uuid") val destUuid: String,
@ColumnInfo(name = "key") val key: ByteArray
) {
fun toModel(): StorageKeyMap {
return StorageKeyMap(
sourceUuid = UUID.fromString(sourceUuid),
destUuid = UUID.fromString(destUuid),
key = EncryptKey(key)
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DbStorageKeyMap
if (sourceUuid != other.sourceUuid) return false
if (destUuid != other.destUuid) return false
if (!key.contentEquals(other.key)) return false
return true
}
override fun hashCode(): Int {
var result = sourceUuid.hashCode()
result = 31 * result + destUuid.hashCode()
result = 31 * result + key.contentHashCode()
return result
}
companion object {
fun fromModel(keymap: StorageKeyMap): DbStorageKeyMap {
return DbStorageKeyMap(
sourceUuid = keymap.sourceUuid.toString(),
destUuid = keymap.destUuid.toString(),
key = keymap.key.bytes
)
}
}
}

View File

@@ -0,0 +1,17 @@
package com.github.nullptroma.wallenc.data.db.app.repository
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKeyMap
import com.github.nullptroma.wallenc.data.model.StorageKeyMap
class StorageKeyMapRepository(private val dao: StorageKeyMapDao) {
fun getAll() = dao.getAll().map { it.toModel() }
fun add(keymap: StorageKeyMap) {
val dbModel = DbStorageKeyMap.fromModel(keymap)
dao.add(dbModel)
}
fun delete(keymap: StorageKeyMap) {
val dbModel = DbStorageKeyMap.fromModel(keymap)
dao.delete(dbModel)
}
}

View File

@@ -0,0 +1,11 @@
package com.github.nullptroma.wallenc.data.model
import androidx.room.ColumnInfo
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import java.util.UUID
data class StorageKeyMap(
val sourceUuid: UUID,
val destUuid: UUID,
val key: EncryptKey
)

View File

@@ -1,34 +1,106 @@
package com.github.nullptroma.wallenc.data.vaults package com.github.nullptroma.wallenc.data.vaults
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao import com.github.nullptroma.wallenc.data.db.app.repository.StorageKeyMapRepository
import com.github.nullptroma.wallenc.data.model.StorageKeyMap
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.encrypt.EncryptedStorage import com.github.nullptroma.wallenc.domain.encrypt.EncryptedStorage
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor
import com.github.nullptroma.wallenc.domain.interfaces.IStorage import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
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 kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import java.util.UUID import java.util.UUID
class UnlockManager(dao: StorageKeyDao, ioDispatcher: CoroutineDispatcher): IUnlockManager { class UnlockManager(
private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>>(mapOf()) private val repo: StorageKeyMapRepository,
override val openedStorages: StateFlow<Map<UUID, IStorage>> private val ioDispatcher: CoroutineDispatcher,
vaultsManager: IVaultsManager
) : IUnlockManager {
private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>?>(null)
override val openedStorages: StateFlow<Map<UUID, IStorage>?>
get() = _openedStorages get() = _openedStorages
val mutex = Mutex()
override fun open( init {
CoroutineScope(ioDispatcher).launch {
vaultsManager.allStorages.collectLatest {
mutex.lock()
val allKeys = repo.getAll()
val allStorages = it.associateBy({ it.uuid }, { it })
val map = _openedStorages.value?.toMutableMap() ?: mutableMapOf()
for(keymap in allKeys) {
if(map.contains(keymap.sourceUuid))
continue
val storage = allStorages[keymap.sourceUuid] ?: continue
val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid)
map[storage.uuid] = encStorage
}
_openedStorages.value = map
mutex.unlock()
}
}
}
private fun createEncryptedStorage(storage: IStorage, key: EncryptKey, uuid: UUID): EncryptedStorage {
return EncryptedStorage(
source = storage,
key = key,
ioDispatcher = ioDispatcher,
uuid = uuid
)
}
override suspend fun open(
storage: IStorage, storage: IStorage,
key: EncryptKey key: EncryptKey
) { ) = withContext(ioDispatcher) {
TODO("Not yet implemented") mutex.lock()
val encInfo = storage.encInfo.value ?: throw Exception("EncInfo is null") // TODO
if (!Encryptor.checkKey(key, encInfo))
throw Exception("Incorrect Key")
if (_openedStorages.value == null) {
val childScope = CoroutineScope(ioDispatcher)
}
val opened = _openedStorages.first { it != null }!!.toMutableMap()
val cur = opened[storage.uuid]
if (cur != null)
throw Exception("Storage is already open")
val keymap = StorageKeyMap(
sourceUuid = storage.uuid,
destUuid = UUID.randomUUID(),
key = key
)
val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid)
opened[storage.uuid] = encStorage
_openedStorages.value = opened
repo.add(keymap)
mutex.unlock()
} }
override fun close(uuid: UUID) { override suspend fun close(storage: IStorage) = withContext(ioDispatcher) {
val enc = _openedStorages.value[uuid] mutex.lock()
if(enc == null) val opened = _openedStorages.first { it != null }!!
return val enc = opened[storage.uuid] ?: return@withContext
_openedStorages.value = _openedStorages.value.toMutableMap().apply { val model = StorageKeyMap(
remove(uuid) sourceUuid = storage.uuid,
destUuid = enc.uuid,
key = EncryptKey("")
)
_openedStorages.value = opened.toMutableMap().apply {
remove(storage.uuid)
} }
enc.dispose() enc.dispose()
repo.delete(model)
mutex.unlock()
} }
} }

View File

@@ -2,6 +2,7 @@ package com.github.nullptroma.wallenc.data.vaults
import android.content.Context import android.content.Context
import com.github.nullptroma.wallenc.data.vaults.local.LocalVault import com.github.nullptroma.wallenc.data.vaults.local.LocalVault
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IVault import com.github.nullptroma.wallenc.domain.interfaces.IVault
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -12,6 +13,8 @@ class VaultsManager(ioDispatcher: CoroutineDispatcher, context: Context) : IVaul
override val remoteVaults: StateFlow<List<IVault>> override val remoteVaults: StateFlow<List<IVault>>
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override val allStorages: StateFlow<List<IStorage>>
get() = localVault.storages
override fun addYandexVault(email: String, token: String) { override fun addYandexVault(email: String, token: String) {
TODO("Not yet implemented") TODO("Not yet implemented")

View File

@@ -77,8 +77,9 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
override suspend fun createStorage( override suspend fun createStorage(
enc: StorageEncryptionInfo enc: StorageEncryptionInfo
): LocalStorage = withContext(ioDispatcher) { ): LocalStorage = withContext(ioDispatcher) {
val storage = createStorage()
TODO("Not yet implemented") storage.setEncInfo(enc)
return@withContext storage
} }
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) { override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {

View File

@@ -3,13 +3,19 @@ package com.github.nullptroma.wallenc.domain.datatypes
import java.security.MessageDigest import java.security.MessageDigest
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
class EncryptKey(val key: String) { class EncryptKey {
fun to32Bytes(): ByteArray { val bytes: ByteArray
constructor(password: String) {
val digest = MessageDigest.getInstance("SHA-256") val digest = MessageDigest.getInstance("SHA-256")
return digest.digest(key.toByteArray(Charsets.UTF_8)) bytes = digest.digest(password.toByteArray(Charsets.UTF_8))
}
constructor(key: ByteArray) {
this.bytes = key.clone()
} }
fun toAesKey() : SecretKeySpec { fun toAesKey() : SecretKeySpec {
return SecretKeySpec(to32Bytes(), "AES") return SecretKeySpec(bytes, "AES")
} }
} }

View File

@@ -6,29 +6,33 @@ import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IStorage import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.util.UUID import java.util.UUID
class EncryptedStorage( class EncryptedStorage(
source: IStorage, private val source: IStorage,
key: EncryptKey, key: EncryptKey,
logger: ILogger,
ioDispatcher: CoroutineDispatcher, ioDispatcher: CoroutineDispatcher,
override val uuid: UUID = UUID.randomUUID()
) : IStorage, DisposableHandle { ) : IStorage, DisposableHandle {
override val size: StateFlow<Long?> override val size: StateFlow<Long?>
get() = TODO("Not yet implemented") get() = source.size
override val numberOfFiles: StateFlow<Int?> override val numberOfFiles: StateFlow<Int?>
get() = TODO("Not yet implemented") get() = source.numberOfFiles
override val uuid: UUID
get() = TODO("Not yet implemented")
override val name: StateFlow<String> override val name: StateFlow<String>
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override val isAvailable: StateFlow<Boolean> override val isAvailable: StateFlow<Boolean>
get() = TODO("Not yet implemented") get() = source.isAvailable
override val encInfo: StateFlow<StorageEncryptionInfo> override val encInfo: StateFlow<StorageEncryptionInfo?>
get() = TODO("Not yet implemented") get() = MutableStateFlow(
StorageEncryptionInfo(
isEncrypted = false,
encryptedTestData = null
)
)
override val accessor: EncryptedStorageAccessor = override val accessor: EncryptedStorageAccessor =
EncryptedStorageAccessor(source.accessor, key, logger, ioDispatcher) EncryptedStorageAccessor(source.accessor, key, ioDispatcher)
override suspend fun rename(newName: String) { override suspend fun rename(newName: String) {
TODO("Not yet implemented") TODO("Not yet implemented")

View File

@@ -28,7 +28,6 @@ import kotlin.io.path.pathString
class EncryptedStorageAccessor( class EncryptedStorageAccessor(
private val source: IStorageAccessor, private val source: IStorageAccessor,
key: EncryptKey, key: EncryptKey,
private val logger: ILogger,
ioDispatcher: CoroutineDispatcher ioDispatcher: CoroutineDispatcher
) : IStorageAccessor, DisposableHandle { ) : IStorageAccessor, DisposableHandle {
private val _job = Job() private val _job = Job()

View File

@@ -8,8 +8,8 @@ interface IUnlockManager {
/** /**
* Хранилища, для которых есть ключ шифрования * Хранилища, для которых есть ключ шифрования
*/ */
val openedStorages: StateFlow<Map<UUID, IStorage>> val openedStorages: StateFlow<Map<UUID, IStorage>?>
fun open(storage: IStorage, key: EncryptKey) suspend fun open(storage: IStorage, key: EncryptKey)
fun close(storage: UUID) suspend fun close(storage: IStorage)
} }

View File

@@ -6,5 +6,6 @@ interface IVaultsManager {
val localVault: IVault val localVault: IVault
val remoteVaults: StateFlow<List<IVault>> val remoteVaults: StateFlow<List<IVault>>
val allStorages: StateFlow<List<IStorage>>
fun addYandexVault(email: String, token: String) fun addYandexVault(email: String, token: String)
} }

View File

@@ -1,8 +0,0 @@
package com.github.nullptroma.wallenc.domain.usecases
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
class GetAllRawStoragesUseCase(private val manager: IVaultsManager) {
val localStorages
get() = manager.localVault.storages
}

View File

@@ -0,0 +1,12 @@
package com.github.nullptroma.wallenc.domain.usecases
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import java.util.UUID
class GetOpenedStoragesUseCase(private val unlockManager: IUnlockManager) {
val openedStorages: StateFlow<Map<UUID, IStorageInfo>?>
get() = unlockManager.openedStorages
}

View File

@@ -1,12 +1,24 @@
package com.github.nullptroma.wallenc.domain.usecases package com.github.nullptroma.wallenc.domain.usecases
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
class ManageLocalVaultUseCase(private val manager: IVaultsManager) { class ManageLocalVaultUseCase(private val manager: IVaultsManager, private val unlockManager: IUnlockManager) {
val localStorages val localStorages: StateFlow<List<IStorageInfo>>
get() = manager.localVault.storages get() = manager.localVault.storages
suspend fun createStorage() { suspend fun createStorage() {
manager.localVault.createStorage() manager.localVault.createStorage()
} }
suspend fun createStorage(key: EncryptKey) {
val encInfo = Encryptor.generateEncryptionInfo(key)
val storage = manager.localVault.createStorage(encInfo)
unlockManager.open(storage, key)
}
} }

View File

@@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.7.1" agp = "8.8.0"
jacksonModuleKotlin = "2.18.2" jacksonModuleKotlin = "2.18.2"
kotlin = "2.0.10" kotlin = "2.0.10"
coreKtx = "1.15.0" coreKtx = "1.15.0"
@@ -10,9 +10,9 @@ kotlinReflect = "2.0.21"
kotlinxCoroutinesCore = "1.9.0" kotlinxCoroutinesCore = "1.9.0"
kotlinxSerializationJson = "1.7.3" kotlinxSerializationJson = "1.7.3"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3" activityCompose = "1.10.0"
composeBom = "2024.10.01" composeBom = "2025.01.00"
navigation = "2.8.3" navigation = "2.8.5"
hiltNavigation = "1.2.0" hiltNavigation = "1.2.0"
timber = "5.0.1" timber = "5.0.1"
yandexAuthSdk = "3.1.2" yandexAuthSdk = "3.1.2"
@@ -68,7 +68,6 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" } material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
[plugins] [plugins]

View File

@@ -1,6 +1,6 @@
#Sat Sep 07 01:04:14 MSK 2024 #Sat Sep 07 01:04:14 MSK 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,15 +1,20 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.ILogger import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
import com.github.nullptroma.wallenc.presentation.viewmodel.ViewModelBase import com.github.nullptroma.wallenc.presentation.viewmodel.ViewModelBase
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@@ -17,13 +22,16 @@ import kotlin.system.measureTimeMillis
@HiltViewModel @HiltViewModel
class LocalVaultViewModel @Inject constructor( class LocalVaultViewModel @Inject constructor(
private val _manageLocalVaultUseCase: ManageLocalVaultUseCase, private val _manageLocalVaultUseCase: ManageLocalVaultUseCase,
private val _getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
private val _storageFileManagementUseCase: StorageFileManagementUseCase, private val _storageFileManagementUseCase: StorageFileManagementUseCase,
private val logger: ILogger private val logger: ILogger
) : ) :
ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) { ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) {
init { init {
viewModelScope.launch { viewModelScope.launch {
_manageLocalVaultUseCase.localStorages.collect { _manageLocalVaultUseCase.localStorages.combine(_getOpenedStoragesUseCase.openedStorages) { local, opened ->
local + (opened?.map { it.value } ?: listOf())
}.collectLatest {
val newState = state.value.copy( val newState = state.value.copy(
storagesList = it storagesList = it
) )
@@ -54,7 +62,7 @@ class LocalVaultViewModel @Inject constructor(
fun createStorage() { fun createStorage() {
viewModelScope.launch { viewModelScope.launch {
_manageLocalVaultUseCase.createStorage() _manageLocalVaultUseCase.createStorage(EncryptKey("hello"))
} }
} }
} }

View File

@@ -82,6 +82,8 @@ controlflow {
<reflist> <reflist>
<ref refid="4652e56b-9248-11ef-8256-d5c6949dbfe2"/> <ref refid="4652e56b-9248-11ef-8256-d5c6949dbfe2"/>
<ref refid="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2"/> <ref refid="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2"/>
<ref refid="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673"/>
<ref refid="cbe7db79-cf20-11ef-9e8c-bf6aa1e99673"/>
</reflist> </reflist>
</nestedPackage> </nestedPackage>
<ownedDiagram> <ownedDiagram>
@@ -341,6 +343,13 @@ existing classes or even new classes with specific responsibilities.</val>
<reflist> <reflist>
<ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/> <ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/>
<ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/> <ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/>
<ref refid="c7e02426-cf20-11ef-946a-bf6aa1e99673"/>
<ref refid="cbe85988-cf20-11ef-88a2-bf6aa1e99673"/>
<ref refid="d9feb676-cf20-11ef-9ba9-bf6aa1e99673"/>
<ref refid="dbd3c61b-cf20-11ef-8f96-bf6aa1e99673"/>
<ref refid="dd4e81d8-cf20-11ef-920e-bf6aa1e99673"/>
<ref refid="e1401072-cf20-11ef-b6f2-bf6aa1e99673"/>
<ref refid="e329e455-cf20-11ef-aab2-bf6aa1e99673"/>
</reflist> </reflist>
</ownedPresentation> </ownedPresentation>
</Diagram> </Diagram>
@@ -410,6 +419,13 @@ existing classes or even new classes with specific responsibilities.</val>
<ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/> <ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/>
</reflist> </reflist>
</presentation> </presentation>
<supplierDependency>
<reflist>
<ref refid="da66c106-cf20-11ef-b314-bf6aa1e99673"/>
<ref refid="dc38c0ec-cf20-11ef-ae61-bf6aa1e99673"/>
<ref refid="ddb583fd-cf20-11ef-9111-bf6aa1e99673"/>
</reflist>
</supplierDependency>
</Package> </Package>
<Diagram id="7a8a9c2d-9248-11ef-bfb6-d5c6949dbfe2"> <Diagram id="7a8a9c2d-9248-11ef-bfb6-d5c6949dbfe2">
<diagramType> <diagramType>
@@ -461,6 +477,11 @@ existing classes or even new classes with specific responsibilities.</val>
</ownedPresentation> </ownedPresentation>
</Diagram> </Diagram>
<Package id="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2"> <Package id="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2">
<clientDependency>
<reflist>
<ref refid="ddb583fd-cf20-11ef-9111-bf6aa1e99673"/>
</reflist>
</clientDependency>
<name> <name>
<val>Data</val> <val>Data</val>
</name> </name>
@@ -477,6 +498,11 @@ existing classes or even new classes with specific responsibilities.</val>
<ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/> <ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/>
</reflist> </reflist>
</presentation> </presentation>
<supplierDependency>
<reflist>
<ref refid="e37cfbf9-cf20-11ef-bb0a-bf6aa1e99673"/>
</reflist>
</supplierDependency>
</Package> </Package>
<Diagram id="647635fd-9249-11ef-9b9a-d5c6949dbfe2"> <Diagram id="647635fd-9249-11ef-9b9a-d5c6949dbfe2">
<diagramType> <diagramType>
@@ -491,7 +517,7 @@ existing classes or even new classes with specific responsibilities.</val>
</Diagram> </Diagram>
<PackageItem id="98c57477-9249-11ef-91a4-d5c6949dbfe2"> <PackageItem id="98c57477-9249-11ef-91a4-d5c6949dbfe2">
<matrix> <matrix>
<val>(1.0, 0.0, 0.0, 1.0, -166.66017150878906, 7.74609375)</val> <val>(1.0, 0.0, 0.0, 1.0, -169.66725884984865, 289.74999999999994)</val>
</matrix> </matrix>
<top-left> <top-left>
<val>(0.0, 0.0)</val> <val>(0.0, 0.0)</val>
@@ -511,10 +537,10 @@ existing classes or even new classes with specific responsibilities.</val>
</PackageItem> </PackageItem>
<PackageItem id="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"> <PackageItem id="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2">
<matrix> <matrix>
<val>(1.0, 0.0, 0.0, 1.0, 171.25390625, 4.573046875000017)</val> <val>(1.0, 0.0, 0.0, 1.0, 168.02553396117582, 0.006953125000009663)</val>
</matrix> </matrix>
<top-left> <top-left>
<val>(-9.948932077648351, 0.0)</val> <val>(0.0, 0.0)</val>
</top-left> </top-left>
<width> <width>
<val>171.94893207764835</val> <val>171.94893207764835</val>
@@ -3681,4 +3707,282 @@ existing classes or even new classes with specific responsibilities.</val>
<ref refid="ed56560a-ca21-11ef-a923-d31b240a181b"/> <ref refid="ed56560a-ca21-11ef-a923-d31b240a181b"/>
</supplier> </supplier>
</Usage> </Usage>
<Package id="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673">
<clientDependency>
<reflist>
<ref refid="dc38c0ec-cf20-11ef-ae61-bf6aa1e99673"/>
<ref refid="e1b05c21-cf20-11ef-a005-bf6aa1e99673"/>
<ref refid="e37cfbf9-cf20-11ef-bb0a-bf6aa1e99673"/>
</reflist>
</clientDependency>
<name>
<val>App</val>
</name>
<package>
<ref refid="f3a82730-71b1-11ec-a409-f47b099bf663"/>
</package>
<presentation>
<reflist>
<ref refid="c7e02426-cf20-11ef-946a-bf6aa1e99673"/>
</reflist>
</presentation>
</Package>
<PackageItem id="c7e02426-cf20-11ef-946a-bf6aa1e99673">
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 188.5, 540.3484910295214)</val>
</matrix>
<top-left>
<val>(0.0, 0.0)</val>
</top-left>
<width>
<val>131.0</val>
</width>
<height>
<val>70.0</val>
</height>
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<subject>
<ref refid="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673"/>
</subject>
</PackageItem>
<Package id="cbe7db79-cf20-11ef-9e8c-bf6aa1e99673">
<clientDependency>
<reflist>
<ref refid="da66c106-cf20-11ef-b314-bf6aa1e99673"/>
</reflist>
</clientDependency>
<name>
<val>Presentation</val>
</name>
<package>
<ref refid="f3a82730-71b1-11ec-a409-f47b099bf663"/>
</package>
<presentation>
<reflist>
<ref refid="cbe85988-cf20-11ef-88a2-bf6aa1e99673"/>
</reflist>
</presentation>
<supplierDependency>
<reflist>
<ref refid="e1b05c21-cf20-11ef-a005-bf6aa1e99673"/>
</reflist>
</supplierDependency>
</Package>
<PackageItem id="cbe85988-cf20-11ef-88a2-bf6aa1e99673">
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 448.06283721542303, 334.1732530596131)</val>
</matrix>
<top-left>
<val>(0.0, 0.0)</val>
</top-left>
<width>
<val>125.0</val>
</width>
<height>
<val>70.0</val>
</height>
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<subject>
<ref refid="cbe7db79-cf20-11ef-9e8c-bf6aa1e99673"/>
</subject>
</PackageItem>
<DependencyItem id="d9feb676-cf20-11ef-9ba9-bf6aa1e99673">
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<horizontal>
<val>0</val>
</horizontal>
<orthogonal>
<val>0</val>
</orthogonal>
<subject>
<ref refid="da66c106-cf20-11ef-b314-bf6aa1e99673"/>
</subject>
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, -60.19342961958796, 358.1635683335607)</val>
</matrix>
<points>
<val>[(345.8748167061808, -178.41052145856074), (559.786266835011, -23.9903152739476)]</val>
</points>
<head-connection>
<ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/>
</head-connection>
<tail-connection>
<ref refid="cbe85988-cf20-11ef-88a2-bf6aa1e99673"/>
</tail-connection>
</DependencyItem>
<Dependency id="da66c106-cf20-11ef-b314-bf6aa1e99673">
<client>
<ref refid="cbe7db79-cf20-11ef-9e8c-bf6aa1e99673"/>
</client>
<presentation>
<reflist>
<ref refid="d9feb676-cf20-11ef-9ba9-bf6aa1e99673"/>
</reflist>
</presentation>
<supplier>
<ref refid="4652e56b-9248-11ef-8256-d5c6949dbfe2"/>
</supplier>
</Dependency>
<DependencyItem id="dbd3c61b-cf20-11ef-8f96-bf6aa1e99673">
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<horizontal>
<val>0</val>
</horizontal>
<orthogonal>
<val>0</val>
</orthogonal>
<subject>
<ref refid="dc38c0ec-cf20-11ef-ae61-bf6aa1e99673"/>
</subject>
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 220.8934129474165, 355.288804110198)</val>
</matrix>
<points>
<val>[(37.80897389480657, -175.53575723519802), (33.10658705258351, -102.53880411019799), (33.10658705258351, 144.861195889802), (33.10658705258351, 185.05968691932338)]</val>
</points>
<head-connection>
<ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/>
</head-connection>
<tail-connection>
<ref refid="c7e02426-cf20-11ef-946a-bf6aa1e99673"/>
</tail-connection>
</DependencyItem>
<Dependency id="dc38c0ec-cf20-11ef-ae61-bf6aa1e99673">
<client>
<ref refid="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673"/>
</client>
<presentation>
<reflist>
<ref refid="dbd3c61b-cf20-11ef-8f96-bf6aa1e99673"/>
</reflist>
</presentation>
<supplier>
<ref refid="4652e56b-9248-11ef-8256-d5c6949dbfe2"/>
</supplier>
</Dependency>
<DependencyItem id="dd4e81d8-cf20-11ef-920e-bf6aa1e99673">
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<horizontal>
<val>0</val>
</horizontal>
<orthogonal>
<val>0</val>
</orthogonal>
<subject>
<ref refid="ddb583fd-cf20-11ef-9111-bf6aa1e99673"/>
</subject>
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 470.30166841962773, 308.92757236917936)</val>
</matrix>
<points>
<val>[(-253.4101148222706, -129.1745254941794), (-484.34892726947635, -19.17757236917936)]</val>
</points>
<head-connection>
<ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/>
</head-connection>
<tail-connection>
<ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/>
</tail-connection>
</DependencyItem>
<Dependency id="ddb583fd-cf20-11ef-9111-bf6aa1e99673">
<client>
<ref refid="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2"/>
</client>
<presentation>
<reflist>
<ref refid="dd4e81d8-cf20-11ef-920e-bf6aa1e99673"/>
</reflist>
</presentation>
<supplier>
<ref refid="4652e56b-9248-11ef-8256-d5c6949dbfe2"/>
</supplier>
</Dependency>
<DependencyItem id="e1401072-cf20-11ef-b6f2-bf6aa1e99673">
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<horizontal>
<val>0</val>
</horizontal>
<orthogonal>
<val>0</val>
</orthogonal>
<subject>
<ref refid="e1b05c21-cf20-11ef-a005-bf6aa1e99673"/>
</subject>
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 171.43286989350588, 363.845719493801)</val>
</matrix>
<points>
<val>[(346.31019988005676, 40.327533565812075), (96.13713010649411, 176.50277153572034)]</val>
</points>
<head-connection>
<ref refid="cbe85988-cf20-11ef-88a2-bf6aa1e99673"/>
</head-connection>
<tail-connection>
<ref refid="c7e02426-cf20-11ef-946a-bf6aa1e99673"/>
</tail-connection>
</DependencyItem>
<Dependency id="e1b05c21-cf20-11ef-a005-bf6aa1e99673">
<client>
<ref refid="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673"/>
</client>
<presentation>
<reflist>
<ref refid="e1401072-cf20-11ef-b6f2-bf6aa1e99673"/>
</reflist>
</presentation>
<supplier>
<ref refid="cbe7db79-cf20-11ef-9e8c-bf6aa1e99673"/>
</supplier>
</Dependency>
<DependencyItem id="e329e455-cf20-11ef-aab2-bf6aa1e99673">
<diagram>
<ref refid="585f224c-71b6-11ec-a409-f47b099bf663"/>
</diagram>
<horizontal>
<val>0</val>
</horizontal>
<orthogonal>
<val>0</val>
</orthogonal>
<subject>
<ref refid="e37cfbf9-cf20-11ef-bb0a-bf6aa1e99673"/>
</subject>
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 243.35250844243774, 348.8318141553794)</val>
</matrix>
<points>
<val>[(-247.11976729228638, 114.31818584462059), (-10.452508442437733, 191.51667687414198)]</val>
</points>
<head-connection>
<ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/>
</head-connection>
<tail-connection>
<ref refid="c7e02426-cf20-11ef-946a-bf6aa1e99673"/>
</tail-connection>
</DependencyItem>
<Dependency id="e37cfbf9-cf20-11ef-bb0a-bf6aa1e99673">
<client>
<ref refid="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673"/>
</client>
<presentation>
<reflist>
<ref refid="e329e455-cf20-11ef-aab2-bf6aa1e99673"/>
</reflist>
</presentation>
<supplier>
<ref refid="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2"/>
</supplier>
</Dependency>
</gaphor> </gaphor>