Реализован UnlockManager
This commit is contained in:
@@ -3,7 +3,7 @@ package com.github.nullptroma.wallenc.app.di.modules.data
|
||||
import android.content.Context
|
||||
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.dao.StorageKeyDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -21,8 +21,8 @@ class RoomModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStorageKeyDao(database: IAppDb): StorageKeyDao {
|
||||
return database.storageKeyDao
|
||||
fun provideStorageKeyDao(database: IAppDb): StorageKeyMapDao {
|
||||
return database.storageKeyMapDao
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -2,7 +2,8 @@ package com.github.nullptroma.wallenc.app.di.modules.data
|
||||
|
||||
import android.content.Context
|
||||
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.VaultsManager
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
|
||||
@@ -25,13 +26,21 @@ class SingletonModule {
|
||||
return VaultsManager(ioDispatcher, context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStorageKeyMapRepository(dao: StorageKeyMapDao): StorageKeyMapRepository {
|
||||
return StorageKeyMapRepository(dao)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUnlockManager(@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
dao: StorageKeyDao): IUnlockManager {
|
||||
repo: StorageKeyMapRepository,
|
||||
vaultsManager: IVaultsManager): IUnlockManager {
|
||||
return UnlockManager(
|
||||
dao = dao,
|
||||
ioDispatcher = ioDispatcher
|
||||
repo = repo,
|
||||
ioDispatcher = ioDispatcher,
|
||||
vaultsManager = vaultsManager
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
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.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.StorageFileManagementUseCase
|
||||
import dagger.Module
|
||||
@@ -15,14 +17,14 @@ import javax.inject.Singleton
|
||||
class UseCasesModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetAllRawStoragesUseCase(vaultsManager: IVaultsManager): GetAllRawStoragesUseCase {
|
||||
return GetAllRawStoragesUseCase(vaultsManager)
|
||||
fun provideGetOpenedStoragesUseCase(unlockManager: IUnlockManager): GetOpenedStoragesUseCase {
|
||||
return GetOpenedStoragesUseCase(unlockManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideManageLocalVaultUseCase(vaultsManager: IVaultsManager): ManageLocalVaultUseCase {
|
||||
return ManageLocalVaultUseCase(vaultsManager)
|
||||
fun provideManageLocalVaultUseCase(vaultsManager: IVaultsManager, unlockManager: IUnlockManager): ManageLocalVaultUseCase {
|
||||
return ManageLocalVaultUseCase(vaultsManager, unlockManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -2,14 +2,14 @@ package com.github.nullptroma.wallenc.data.db.app
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKey
|
||||
import com.github.nullptroma.wallenc.data.db.app.dao.StorageKeyMapDao
|
||||
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKeyMap
|
||||
|
||||
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 override val storageKeyDao: StorageKeyDao
|
||||
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -1,34 +1,106 @@
|
||||
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.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.IUnlockManager
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
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
|
||||
|
||||
class UnlockManager(dao: StorageKeyDao, ioDispatcher: CoroutineDispatcher): IUnlockManager {
|
||||
private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>>(mapOf())
|
||||
override val openedStorages: StateFlow<Map<UUID, IStorage>>
|
||||
class UnlockManager(
|
||||
private val repo: StorageKeyMapRepository,
|
||||
private val ioDispatcher: CoroutineDispatcher,
|
||||
vaultsManager: IVaultsManager
|
||||
) : IUnlockManager {
|
||||
private val _openedStorages = MutableStateFlow<Map<UUID, EncryptedStorage>?>(null)
|
||||
override val openedStorages: StateFlow<Map<UUID, IStorage>?>
|
||||
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,
|
||||
key: EncryptKey
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
) = withContext(ioDispatcher) {
|
||||
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) {
|
||||
val enc = _openedStorages.value[uuid]
|
||||
if(enc == null)
|
||||
return
|
||||
_openedStorages.value = _openedStorages.value.toMutableMap().apply {
|
||||
remove(uuid)
|
||||
override suspend fun close(storage: IStorage) = withContext(ioDispatcher) {
|
||||
mutex.lock()
|
||||
val opened = _openedStorages.first { it != null }!!
|
||||
val enc = opened[storage.uuid] ?: return@withContext
|
||||
val model = StorageKeyMap(
|
||||
sourceUuid = storage.uuid,
|
||||
destUuid = enc.uuid,
|
||||
key = EncryptKey("")
|
||||
)
|
||||
_openedStorages.value = opened.toMutableMap().apply {
|
||||
remove(storage.uuid)
|
||||
}
|
||||
enc.dispose()
|
||||
repo.delete(model)
|
||||
mutex.unlock()
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.github.nullptroma.wallenc.data.vaults
|
||||
|
||||
import android.content.Context
|
||||
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.IVaultsManager
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -12,6 +13,8 @@ class VaultsManager(ioDispatcher: CoroutineDispatcher, context: Context) : IVaul
|
||||
|
||||
override val remoteVaults: StateFlow<List<IVault>>
|
||||
get() = TODO("Not yet implemented")
|
||||
override val allStorages: StateFlow<List<IStorage>>
|
||||
get() = localVault.storages
|
||||
|
||||
override fun addYandexVault(email: String, token: String) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
@@ -77,8 +77,9 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context
|
||||
override suspend fun createStorage(
|
||||
enc: StorageEncryptionInfo
|
||||
): LocalStorage = withContext(ioDispatcher) {
|
||||
|
||||
TODO("Not yet implemented")
|
||||
val storage = createStorage()
|
||||
storage.setEncInfo(enc)
|
||||
return@withContext storage
|
||||
}
|
||||
|
||||
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
|
||||
|
||||
@@ -3,13 +3,19 @@ package com.github.nullptroma.wallenc.domain.datatypes
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class EncryptKey(val key: String) {
|
||||
fun to32Bytes(): ByteArray {
|
||||
class EncryptKey {
|
||||
val bytes: ByteArray
|
||||
|
||||
constructor(password: String) {
|
||||
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 {
|
||||
return SecretKeySpec(to32Bytes(), "AES")
|
||||
return SecretKeySpec(bytes, "AES")
|
||||
}
|
||||
}
|
||||
@@ -6,29 +6,33 @@ import com.github.nullptroma.wallenc.domain.interfaces.ILogger
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.UUID
|
||||
|
||||
class EncryptedStorage(
|
||||
source: IStorage,
|
||||
private val source: IStorage,
|
||||
key: EncryptKey,
|
||||
logger: ILogger,
|
||||
ioDispatcher: CoroutineDispatcher,
|
||||
override val uuid: UUID = UUID.randomUUID()
|
||||
) : IStorage, DisposableHandle {
|
||||
override val size: StateFlow<Long?>
|
||||
get() = TODO("Not yet implemented")
|
||||
get() = source.size
|
||||
override val numberOfFiles: StateFlow<Int?>
|
||||
get() = TODO("Not yet implemented")
|
||||
override val uuid: UUID
|
||||
get() = TODO("Not yet implemented")
|
||||
get() = source.numberOfFiles
|
||||
override val name: StateFlow<String>
|
||||
get() = TODO("Not yet implemented")
|
||||
override val isAvailable: StateFlow<Boolean>
|
||||
get() = TODO("Not yet implemented")
|
||||
override val encInfo: StateFlow<StorageEncryptionInfo>
|
||||
get() = TODO("Not yet implemented")
|
||||
get() = source.isAvailable
|
||||
override val encInfo: StateFlow<StorageEncryptionInfo?>
|
||||
get() = MutableStateFlow(
|
||||
StorageEncryptionInfo(
|
||||
isEncrypted = false,
|
||||
encryptedTestData = null
|
||||
)
|
||||
)
|
||||
override val accessor: EncryptedStorageAccessor =
|
||||
EncryptedStorageAccessor(source.accessor, key, logger, ioDispatcher)
|
||||
EncryptedStorageAccessor(source.accessor, key, ioDispatcher)
|
||||
|
||||
override suspend fun rename(newName: String) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
@@ -28,7 +28,6 @@ import kotlin.io.path.pathString
|
||||
class EncryptedStorageAccessor(
|
||||
private val source: IStorageAccessor,
|
||||
key: EncryptKey,
|
||||
private val logger: ILogger,
|
||||
ioDispatcher: CoroutineDispatcher
|
||||
) : IStorageAccessor, DisposableHandle {
|
||||
private val _job = Job()
|
||||
|
||||
@@ -8,8 +8,8 @@ interface IUnlockManager {
|
||||
/**
|
||||
* Хранилища, для которых есть ключ шифрования
|
||||
*/
|
||||
val openedStorages: StateFlow<Map<UUID, IStorage>>
|
||||
val openedStorages: StateFlow<Map<UUID, IStorage>?>
|
||||
|
||||
fun open(storage: IStorage, key: EncryptKey)
|
||||
fun close(storage: UUID)
|
||||
suspend fun open(storage: IStorage, key: EncryptKey)
|
||||
suspend fun close(storage: IStorage)
|
||||
}
|
||||
@@ -6,5 +6,6 @@ interface IVaultsManager {
|
||||
val localVault: IVault
|
||||
val remoteVaults: StateFlow<List<IVault>>
|
||||
|
||||
val allStorages: StateFlow<List<IStorage>>
|
||||
fun addYandexVault(email: String, token: String)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
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 kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ManageLocalVaultUseCase(private val manager: IVaultsManager) {
|
||||
val localStorages
|
||||
class ManageLocalVaultUseCase(private val manager: IVaultsManager, private val unlockManager: IUnlockManager) {
|
||||
val localStorages: StateFlow<List<IStorageInfo>>
|
||||
get() = manager.localVault.storages
|
||||
|
||||
suspend fun createStorage() {
|
||||
manager.localVault.createStorage()
|
||||
}
|
||||
|
||||
suspend fun createStorage(key: EncryptKey) {
|
||||
val encInfo = Encryptor.generateEncryptionInfo(key)
|
||||
val storage = manager.localVault.createStorage(encInfo)
|
||||
unlockManager.open(storage, key)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.7.1"
|
||||
agp = "8.8.0"
|
||||
jacksonModuleKotlin = "2.18.2"
|
||||
kotlin = "2.0.10"
|
||||
coreKtx = "1.15.0"
|
||||
@@ -10,9 +10,9 @@ kotlinReflect = "2.0.21"
|
||||
kotlinxCoroutinesCore = "1.9.0"
|
||||
kotlinxSerializationJson = "1.7.3"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
activityCompose = "1.9.3"
|
||||
composeBom = "2024.10.01"
|
||||
navigation = "2.8.3"
|
||||
activityCompose = "1.10.0"
|
||||
composeBom = "2025.01.00"
|
||||
navigation = "2.8.5"
|
||||
hiltNavigation = "1.2.0"
|
||||
timber = "5.0.1"
|
||||
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-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
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]
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sat Sep 07 01:04:14 MSK 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
|
||||
|
||||
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.IFile
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
|
||||
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.StorageFileManagementUseCase
|
||||
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
|
||||
import com.github.nullptroma.wallenc.presentation.viewmodel.ViewModelBase
|
||||
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 javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -17,13 +22,16 @@ import kotlin.system.measureTimeMillis
|
||||
@HiltViewModel
|
||||
class LocalVaultViewModel @Inject constructor(
|
||||
private val _manageLocalVaultUseCase: ManageLocalVaultUseCase,
|
||||
private val _getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
|
||||
private val _storageFileManagementUseCase: StorageFileManagementUseCase,
|
||||
private val logger: ILogger
|
||||
) :
|
||||
ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf())) {
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
_manageLocalVaultUseCase.localStorages.collect {
|
||||
_manageLocalVaultUseCase.localStorages.combine(_getOpenedStoragesUseCase.openedStorages) { local, opened ->
|
||||
local + (opened?.map { it.value } ?: listOf())
|
||||
}.collectLatest {
|
||||
val newState = state.value.copy(
|
||||
storagesList = it
|
||||
)
|
||||
@@ -54,7 +62,7 @@ class LocalVaultViewModel @Inject constructor(
|
||||
|
||||
fun createStorage() {
|
||||
viewModelScope.launch {
|
||||
_manageLocalVaultUseCase.createStorage()
|
||||
_manageLocalVaultUseCase.createStorage(EncryptKey("hello"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,8 @@ controlflow {
|
||||
<reflist>
|
||||
<ref refid="4652e56b-9248-11ef-8256-d5c6949dbfe2"/>
|
||||
<ref refid="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2"/>
|
||||
<ref refid="c7dfafe9-cf20-11ef-ac08-bf6aa1e99673"/>
|
||||
<ref refid="cbe7db79-cf20-11ef-9e8c-bf6aa1e99673"/>
|
||||
</reflist>
|
||||
</nestedPackage>
|
||||
<ownedDiagram>
|
||||
@@ -341,6 +343,13 @@ existing classes or even new classes with specific responsibilities.</val>
|
||||
<reflist>
|
||||
<ref refid="98c57477-9249-11ef-91a4-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>
|
||||
</ownedPresentation>
|
||||
</Diagram>
|
||||
@@ -410,6 +419,13 @@ existing classes or even new classes with specific responsibilities.</val>
|
||||
<ref refid="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2"/>
|
||||
</reflist>
|
||||
</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>
|
||||
<Diagram id="7a8a9c2d-9248-11ef-bfb6-d5c6949dbfe2">
|
||||
<diagramType>
|
||||
@@ -461,6 +477,11 @@ existing classes or even new classes with specific responsibilities.</val>
|
||||
</ownedPresentation>
|
||||
</Diagram>
|
||||
<Package id="4dc1ae5b-9249-11ef-80b9-d5c6949dbfe2">
|
||||
<clientDependency>
|
||||
<reflist>
|
||||
<ref refid="ddb583fd-cf20-11ef-9111-bf6aa1e99673"/>
|
||||
</reflist>
|
||||
</clientDependency>
|
||||
<name>
|
||||
<val>Data</val>
|
||||
</name>
|
||||
@@ -477,6 +498,11 @@ existing classes or even new classes with specific responsibilities.</val>
|
||||
<ref refid="98c57477-9249-11ef-91a4-d5c6949dbfe2"/>
|
||||
</reflist>
|
||||
</presentation>
|
||||
<supplierDependency>
|
||||
<reflist>
|
||||
<ref refid="e37cfbf9-cf20-11ef-bb0a-bf6aa1e99673"/>
|
||||
</reflist>
|
||||
</supplierDependency>
|
||||
</Package>
|
||||
<Diagram id="647635fd-9249-11ef-9b9a-d5c6949dbfe2">
|
||||
<diagramType>
|
||||
@@ -491,7 +517,7 @@ existing classes or even new classes with specific responsibilities.</val>
|
||||
</Diagram>
|
||||
<PackageItem id="98c57477-9249-11ef-91a4-d5c6949dbfe2">
|
||||
<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>
|
||||
<top-left>
|
||||
<val>(0.0, 0.0)</val>
|
||||
@@ -511,10 +537,10 @@ existing classes or even new classes with specific responsibilities.</val>
|
||||
</PackageItem>
|
||||
<PackageItem id="9aa5be3e-9249-11ef-80b4-d5c6949dbfe2">
|
||||
<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>
|
||||
<top-left>
|
||||
<val>(-9.948932077648351, 0.0)</val>
|
||||
<val>(0.0, 0.0)</val>
|
||||
</top-left>
|
||||
<width>
|
||||
<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"/>
|
||||
</supplier>
|
||||
</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>
|
||||
Reference in New Issue
Block a user