Реализован UnlockManager
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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]
|
||||||
|
|||||||
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
|
#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
|
||||||
|
|||||||
@@ -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"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user