Отображение хранилищ как деревья

This commit is contained in:
Пытков Роман
2025-01-27 22:19:44 +03:00
parent 851fb6bd88
commit eba0d0e3cb
9 changed files with 129 additions and 32 deletions

View File

@@ -6,7 +6,7 @@ plugins {
android { android {
namespace = "com.github.nullptroma.wallenc.data" namespace = "com.github.nullptroma.wallenc.data"
compileSdk = 34 compileSdk = 35
defaultConfig { defaultConfig {
minSdk = 26 minSdk = 26

View File

@@ -28,4 +28,7 @@ interface StorageMetaInfoDao {
@Delete @Delete
suspend fun delete(metaInfo: DbStorageMetaInfo) suspend fun delete(metaInfo: DbStorageMetaInfo)
@Query("DELETE FROM storage_meta_infos WHERE uuid == :uuid")
suspend fun delete(uuid: UUID)
} }

View File

@@ -3,6 +3,7 @@ package com.github.nullptroma.wallenc.data.db.app.repository
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.nullptroma.wallenc.data.db.app.dao.StorageMetaInfoDao import com.github.nullptroma.wallenc.data.db.app.dao.StorageMetaInfoDao
import com.github.nullptroma.wallenc.data.db.app.model.DbStorageMetaInfo import com.github.nullptroma.wallenc.data.db.app.model.DbStorageMetaInfo
import com.github.nullptroma.wallenc.data.utils.IProvider
import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -34,6 +35,11 @@ class StorageMetaInfoRepository(
dao.add(DbStorageMetaInfo(uuid, json)) dao.add(DbStorageMetaInfo(uuid, json))
} }
suspend fun delete(uuid: UUID) = withContext(ioDispatcher) {
dao.delete(uuid)
}
fun createSingleStorageProvider(uuid: UUID): SingleStorageMetaInfoProvider { fun createSingleStorageProvider(uuid: UUID): SingleStorageMetaInfoProvider {
return SingleStorageMetaInfoProvider(this, uuid) return SingleStorageMetaInfoProvider(this, uuid)
} }
@@ -41,9 +47,10 @@ class StorageMetaInfoRepository(
class SingleStorageMetaInfoProvider ( class SingleStorageMetaInfoProvider (
private val repo: StorageMetaInfoRepository, private val repo: StorageMetaInfoRepository,
val uuid: UUID val uuid: UUID
) { ) : IProvider<CommonStorageMetaInfo> {
suspend fun get(): CommonStorageMetaInfo? = repo.getMeta(uuid) override suspend fun get(): CommonStorageMetaInfo? = repo.getMeta(uuid)
suspend fun set(metaInfo: CommonStorageMetaInfo) = repo.setMeta(uuid, metaInfo) override suspend fun clear() = repo.delete(uuid)
override suspend fun set(value: CommonStorageMetaInfo) = repo.setMeta(uuid, value)
} }
companion object { companion object {

View File

@@ -0,0 +1,7 @@
package com.github.nullptroma.wallenc.data.utils
interface IProvider<T> {
suspend fun get(): T?
suspend fun set(value: T)
suspend fun clear()
}

View File

@@ -0,0 +1,3 @@
package com.github.nullptroma.wallenc.domain.datatypes
class Tree<T>(val value: T, var children: List<Tree<T>>? = null)

View File

@@ -1,32 +1,60 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.domain.datatypes.Tree
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
import com.github.nullptroma.wallenc.presentation.R
import kotlin.random.Random
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LocalVaultScreen(modifier: Modifier = Modifier, fun LocalVaultScreen(
viewModel: LocalVaultViewModel = hiltViewModel()) { modifier: Modifier = Modifier,
viewModel: LocalVaultViewModel = hiltViewModel()
) {
val uiState by viewModel.state.collectAsStateWithLifecycle() val uiState by viewModel.state.collectAsStateWithLifecycle()
Scaffold(modifier = modifier, contentWindowInsets = WindowInsets(0.dp), floatingActionButton = { Scaffold(modifier = modifier, contentWindowInsets = WindowInsets(0.dp), floatingActionButton = {
@@ -39,27 +67,63 @@ fun LocalVaultScreen(modifier: Modifier = Modifier,
} }
}) { innerPadding -> }) { innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
items(uiState.storagesList) { items(uiState.storagesList) { tree ->
Card(modifier = Modifier.clickable { }.pointerInput(Unit) { Storage(Modifier.padding(8.dp), tree)
detectTapGestures( }
onTap = { _ -> viewModel.printStorageInfoToLog(it) } }
) }
}.padding(8.dp)) { }
val available by it.isAvailable.collectAsStateWithLifecycle()
val numOfFiles by it.numberOfFiles.collectAsStateWithLifecycle()
val size by it.size.collectAsStateWithLifecycle()
val metaInfo by it.metaInfo.collectAsStateWithLifecycle()
val enc = metaInfo.encInfo @Composable
Column(modifier = Modifier.padding(4.dp)) { fun Storage(modifier: Modifier, tree: Tree<IStorageInfo>) {
Text(it.uuid.toString()) val cur = tree.value
Text("IsAvailable: $available") val cardShape = RoundedCornerShape(30.dp)
Column(modifier) {
Card(
modifier = Modifier
.fillMaxWidth()
.clip(cardShape)
.clickable {
//viewModel.printStorageInfoToLog(cur)
},
shape = cardShape,
elevation = CardDefaults.cardElevation(
defaultElevation = 4.dp
),
) {
val available by cur.isAvailable.collectAsStateWithLifecycle()
val numOfFiles by cur.numberOfFiles.collectAsStateWithLifecycle()
val size by cur.size.collectAsStateWithLifecycle()
val metaInfo by cur.metaInfo.collectAsStateWithLifecycle()
Row {
Column(modifier = Modifier.padding(8.dp)) {
Text(metaInfo.name ?: stringResource(R.string.no_name))
Text(
text = "IsAvailable: $available"
)
Text("Files: $numOfFiles") Text("Files: $numOfFiles")
Text("Size: $size") Text("Size: $size")
Text("Enc: $enc") Text("IsVirtual: ${cur.isVirtualStorage}")
Text(
modifier = Modifier
.fillMaxWidth()
.padding(0.dp,0.dp,8.dp,0.dp)
.align(Alignment.End),
text = cur.uuid.toString(),
textAlign = TextAlign.End,
fontSize = 8.sp,
style = LocalTextStyle.current.copy(
platformStyle = PlatformTextStyle(
includeFontPadding = true
)
)
)
} }
} }
} }
for(i in tree.children ?: listOf()) {
Storage(Modifier.padding(16.dp,0.dp,0.dp,0.dp), i)
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import com.github.nullptroma.wallenc.domain.datatypes.Tree
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
data class LocalVaultScreenState(val storagesList: List<IStorageInfo>) data class LocalVaultScreenState(val storagesList: List<Tree<IStorageInfo>>)

View File

@@ -2,6 +2,7 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.va
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.Tree
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
@@ -24,12 +25,22 @@ class LocalVaultViewModel @Inject constructor(
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase, 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.combine(getOpenedStoragesUseCase.openedStorages) { local, opened -> manageLocalVaultUseCase.localStorages.combine(getOpenedStoragesUseCase.openedStorages) { local, opened ->
local + (opened?.map { it.value } ?: listOf()) val list = mutableListOf<Tree<IStorageInfo>>()
for (storage in local) {
var tree = Tree(storage)
list.add(tree)
while(opened != null && opened.containsKey(tree.value.uuid)) {
val child = opened.getValue(tree.value.uuid)
val nextTree = Tree(child)
tree.children = listOf(nextTree)
tree = nextTree
}
}
return@combine list
}.collectLatest { }.collectLatest {
val newState = state.value.copy( val newState = state.value.copy(
storagesList = it storagesList = it
@@ -61,7 +72,7 @@ class LocalVaultViewModel @Inject constructor(
fun createStorage() { fun createStorage() {
viewModelScope.launch { viewModelScope.launch {
manageLocalVaultUseCase.createStorage(EncryptKey("hello")) manageLocalVaultUseCase.createStorage()
} }
} }
} }

View File

@@ -6,4 +6,5 @@
<string name="nav_label_settings">Settings</string> <string name="nav_label_settings">Settings</string>
<string name="settings_title">Settings Screen Title!</string> <string name="settings_title">Settings Screen Title!</string>
<string name="no_name">&lt;noname&gt;</string>
</resources> </resources>