diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 08963e3..31cef83 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,7 +12,7 @@ android { defaultConfig { applicationId = "com.github.nullptroma.wallenc.app" - minSdk = 24 + minSdk = 26 targetSdk = 34 versionCode = 1 versionName = "1.0" diff --git a/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt b/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt index d3e21ff..0f15b66 100644 --- a/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt +++ b/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt @@ -2,6 +2,7 @@ package com.github.nullptroma.wallenc.app.di.modules.domain import com.github.nullptroma.wallenc.domain.models.IVaultsManager import com.github.nullptroma.wallenc.domain.usecases.GetAllRawStoragesUseCase +import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -16,4 +17,10 @@ class UseCasesModule { fun provideGetAllRawStoragesUseCase(vaultsManager: IVaultsManager): GetAllRawStoragesUseCase { return GetAllRawStoragesUseCase(vaultsManager) } + + @Provides + @Singleton + fun provideStorageFileManagementUseCase(): StorageFileManagementUseCase { + return StorageFileManagementUseCase() + } } \ No newline at end of file diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 1b70c8d..49a2bee 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -9,7 +9,7 @@ android { compileSdk = 34 defaultConfig { - minSdk = 24 + minSdk = 26 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") @@ -34,6 +34,10 @@ android { } dependencies { + // jackson + implementation(libs.jackson.module.kotlin) + implementation(libs.jackson.datatype.jsr310) + // Timber implementation(libs.timber) @@ -45,9 +49,8 @@ dependencies { // Retrofit implementation(libs.retrofit) - implementation(libs.retrofit.converter.gson) implementation(libs.retrofit.converter.scalars) - implementation(libs.google.gson) + implementation(libs.retrofit.converter.jackson) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt index 4a92bb8..34fbf46 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalStorageAccessor.kt @@ -1,6 +1,14 @@ package com.github.nullptroma.wallenc.data.vaults.local +import com.fasterxml.jackson.core.JacksonException +import com.fasterxml.jackson.core.JsonParseException +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalDirectory +import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalFile +import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalMetaInfo import com.github.nullptroma.wallenc.domain.datatypes.DataPackage +import com.github.nullptroma.wallenc.domain.datatypes.DataPage import com.github.nullptroma.wallenc.domain.models.IDirectory import com.github.nullptroma.wallenc.domain.models.IFile import com.github.nullptroma.wallenc.domain.models.IStorageAccessor @@ -11,90 +19,256 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import java.io.File import java.io.InputStream import java.io.OutputStream +import java.nio.file.Path +import java.time.LocalDateTime import kotlin.io.path.Path +import kotlin.io.path.absolute import kotlin.io.path.fileSize +import kotlin.io.path.pathString +import kotlin.io.path.relativeTo class LocalStorageAccessor( - private val absolutePath: String, + absolutePath: String, private val ioDispatcher: CoroutineDispatcher ) : IStorageAccessor { - private val _size = MutableStateFlow(null) - private val _numberOfFiles = MutableStateFlow(null) - private val _isAvailable = MutableStateFlow(false) - private val _filesUpdates = MutableSharedFlow>() - private val _dirsUpdates = MutableSharedFlow>() + private val _absolutePath: Path = Path(absolutePath).normalize().absolute() + private val _jackson = jacksonObjectMapper().apply { findAndRegisterModules() } - override val size: StateFlow - get() = _size - override val numberOfFiles: StateFlow - get() = _numberOfFiles - override val isAvailable: StateFlow - get() = _isAvailable - override val filesUpdates: SharedFlow> - get() = _filesUpdates - override val dirsUpdates: SharedFlow> - get() = _dirsUpdates + private val _size = MutableStateFlow(null) + override val size: StateFlow = _size + + private val _numberOfFiles = MutableStateFlow(null) + override val numberOfFiles: StateFlow = _numberOfFiles + + private val _isAvailable = MutableStateFlow(false) + override val isAvailable: StateFlow = _isAvailable + + private val _filesUpdates = MutableSharedFlow>() + override val filesUpdates: SharedFlow> = _filesUpdates + + private val _dirsUpdates = MutableSharedFlow>() + override val dirsUpdates: SharedFlow> = _dirsUpdates init { + // запускам сканирование хранилища CoroutineScope(ioDispatcher).launch { - scanStorage() + Timber.d("Local storage path: $_absolutePath") + updateSizeAndNumOfFiles() } } + /** + * Проверяет существование корневого пути Storage в файловой системе, изменяет _isAvailable + */ private fun checkAvailable(): Boolean { - _isAvailable.value = File(absolutePath).exists() + _isAvailable.value = _absolutePath.toFile().exists() return _isAvailable.value } - private fun forAllFiles(dir: File, callback: (File) -> Unit) { - if (dir.exists() == false) + /** + * Перебирает все файлы в файловой системе + * @param dir стартовый каталог + * @param maxDepth максимальная глубина (отрицательное для бесконечной) + * @param callback метод обратного вызова для каждого файла и директории + */ + private suspend fun scanFileSystem( + dir: File, + maxDepth: Int, + callback: suspend (File) -> Unit, + useCallbackForSelf: Boolean = true + ) { + if (!dir.exists()) return - callback(dir) - val nextDirs = dir.listFiles() - if (nextDirs != null) { - for (nextDir in nextDirs) { - forAllFiles(nextDir, callback) + + val children = dir.listFiles() + if (children != null) { + // вызвать коллбек для каждого элемента директории + for (child in children) { + callback(child) + } + + if (maxDepth != 0) { + val nextMaxDepth = if (maxDepth > 0) maxDepth - 1 else maxDepth + for (child in children) { + if (child.isDirectory) { + scanFileSystem(child, nextMaxDepth, callback, false) + } + } } } + + if (useCallbackForSelf) + callback(dir) } - private suspend fun scanStorage() = withContext(ioDispatcher) { - _isAvailable.value = File(absolutePath).exists() + /** + * Перебирает все файлы и каталоги в relativePath и возвращает с мета-информацией + * + */ + private suspend fun scanStorage( + baseStoragePath: String, + maxDepth: Int, + fileCallback: suspend (LocalFile) -> Unit = {}, + dirCallback: suspend (LocalDirectory) -> Unit = {} + ) { + if (!checkAvailable()) + throw Exception("Not available") + val basePath = Path(_absolutePath.pathString, baseStoragePath) + scanFileSystem(basePath.toFile(), maxDepth, { file -> + val filePath = Path(file.absolutePath) + + // если это файл с мета-информацией - пропустить + if (filePath.pathString.endsWith(META_INFO_POSTFIX)) { + // Если не удаётся прочитать метаданные или они указывают на несуществующий файл - удалить + try { + val reader = file.bufferedReader() + val meta : LocalMetaInfo = _jackson.readValue(reader) + val fileInMeta = File(Path(_absolutePath.pathString, meta.path).pathString) + if (!fileInMeta.exists()) + file.delete() + } catch (e: JacksonException) { + file.delete() + } + return@scanFileSystem + } + + val metaFilePath = Path( + if (file.isFile) { + file.absolutePath + META_INFO_POSTFIX + } else { + Path(file.absolutePath, META_INFO_POSTFIX).pathString + } + ) + val metaFile = metaFilePath.toFile() + val metaInfo: LocalMetaInfo + val storageFilePath = "/" + filePath.relativeTo(_absolutePath) + + if (!metaFile.exists()) { + metaInfo = createNewLocalMetaInfo(storageFilePath, filePath.fileSize()) + _jackson.writeValue(metaFile, metaInfo) + } else { + var readMeta: LocalMetaInfo + try { + val reader = metaFile.bufferedReader() + readMeta = _jackson.readValue(reader) + } catch (e: JacksonException) { + // если файл повреждён - пересоздать + readMeta = createNewLocalMetaInfo(storageFilePath, filePath.fileSize()) + _jackson.writeValue(metaFile, readMeta) + } + metaInfo = readMeta + } + + if (file.isFile) { + fileCallback(LocalFile(metaInfo)) + } else { + dirCallback(LocalDirectory(metaInfo, null)) + } + }) + } + + /** + * Создаёт LocalMetaInfo, не требуя наличие файла в файловой системе + * @param storagePath полный путь в Storage + * @param size размер файла + */ + private fun createNewLocalMetaInfo(storagePath: String, size: Long): LocalMetaInfo { + return LocalMetaInfo( + size = size, + isDeleted = false, + isHidden = false, + lastModified = LocalDateTime.now(), + path = storagePath + ) + } + + + /** + * Считает файлы и их размер. Не бросает исключения, если файлы недоступны + * @throws none Если возникла ошибка, оставляет размер и количества файлов равными null + */ + private suspend fun updateSizeAndNumOfFiles() { + if (!checkAvailable()) { + _size.value = null + _numberOfFiles.value = null + return + } var size = 0L var numOfFiles = 0 - forAllFiles(File(absolutePath)) { - if (it.isFile) { - numOfFiles++ - size += Path(it.path).fileSize() - } - } + scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { + size += it.metaInfo.size + numOfFiles++ + }) + _size.value = size _numberOfFiles.value = numOfFiles } override suspend fun getAllFiles(): List = withContext(ioDispatcher) { - if(checkAvailable() == false) + if (!checkAvailable()) return@withContext listOf() val list = mutableListOf() - return@withContext listOf() + scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { + list.add(it) + }) + return@withContext list } override suspend fun getFiles(path: String): List = withContext(ioDispatcher) { - TODO("Not yet implemented") + if (!checkAvailable()) + return@withContext listOf() + + val list = mutableListOf() + scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { + list.add(it) + }) + return@withContext list } - override fun getFilesFlow(path: String): Flow> { - TODO("Not yet implemented") - } + override fun getFilesFlow(path: String): Flow>> = flow { + if (!checkAvailable()) + return@flow + + val buf = mutableListOf() + var pageNumber = 0 + scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { + if(buf.size == DATA_PAGE_LENGTH) { + val page = DataPage( + list = buf.toList(), + isLoading = false, + isError = false, + hasNext = true, + pageLength = DATA_PAGE_LENGTH, + pageIndex = pageNumber++ + ) + emit(page) + buf.clear() + } + buf.add(it) + }) + // отправка последней страницы + val page = DataPage( + list = buf.toList(), + isLoading = false, + isError = false, + hasNext = false, + pageLength = DATA_PAGE_LENGTH, + pageIndex = pageNumber++ + ) + emit(page) + }.flowOn(ioDispatcher) override suspend fun getAllDirs(): List = withContext(ioDispatcher) { TODO("Not yet implemented") @@ -104,7 +278,7 @@ class LocalStorageAccessor( TODO("Not yet implemented") } - override fun getDirsFlow(path: String): Flow> { + override fun getDirsFlow(path: String): Flow>> { TODO("Not yet implemented") } @@ -139,4 +313,9 @@ class LocalStorageAccessor( override suspend fun moveToTrash(path: String) = withContext(ioDispatcher) { TODO("Not yet implemented") } + + companion object { + private const val META_INFO_POSTFIX = ".wallenc-meta" + private const val DATA_PAGE_LENGTH = 10 + } } \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalVault.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalVault.kt index 9be1cc6..ab7a921 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalVault.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/LocalVault.kt @@ -18,12 +18,23 @@ import kotlin.io.path.createDirectory import kotlin.io.path.pathString class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context) : IVault { - private val _path = MutableStateFlow(null) - private val _storages = MutableStateFlow(listOf()) - private val _totalSpace = MutableStateFlow(null) - private val _availableSpace = MutableStateFlow(null) - private val _isAvailable = MutableStateFlow(false) + override val type: VaultType = VaultType.LOCAL + override val uuid: UUID + get() = TODO("Not yet implemented") + private val _storages = MutableStateFlow(listOf()) + override val storages: StateFlow> = _storages + + private val _isAvailable = MutableStateFlow(false) + override val isAvailable: StateFlow = _isAvailable + + private val _totalSpace = MutableStateFlow(null) + override val totalSpace: StateFlow = _totalSpace + + private val _availableSpace = MutableStateFlow(null) + override val availableSpace: StateFlow = _availableSpace + + private val _path = MutableStateFlow(null) init { CoroutineScope(ioDispatcher).launch { @@ -35,7 +46,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context private fun readStorages() { val path = _path.value - if (path == null || _isAvailable.value == false) + if (path == null || !_isAvailable.value) return val dirs = path.listFiles()?.filter { it.isDirectory } @@ -49,7 +60,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context override suspend fun createStorage(): IStorage = withContext(ioDispatcher) { val path = _path.value - if (path == null || _isAvailable.value == false) + if (path == null || !_isAvailable.value) throw Exception("Not available") val uuid = UUID.randomUUID() @@ -78,16 +89,4 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) { TODO("Not yet implemented") } - - override val type: VaultType = VaultType.LOCAL - override val uuid: UUID - get() = TODO("Not yet implemented") - override val storages: StateFlow> - get() = _storages - override val isAvailable: StateFlow - get() = _isAvailable - override val totalSpace: StateFlow - get() = _totalSpace - override val availableSpace: StateFlow - get() = _availableSpace } \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalDirectory.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalDirectory.kt index 9ed3f46..37d1797 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalDirectory.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalDirectory.kt @@ -2,7 +2,7 @@ package com.github.nullptroma.wallenc.data.vaults.local.entity import com.github.nullptroma.wallenc.domain.models.IDirectory -class LocalDirectory( +data class LocalDirectory( override val metaInfo: LocalMetaInfo, - override val elementsCount: Int + override val elementsCount: Int? ) : IDirectory \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalFile.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalFile.kt index ccbead2..471cb7c 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalFile.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalFile.kt @@ -2,4 +2,4 @@ package com.github.nullptroma.wallenc.data.vaults.local.entity import com.github.nullptroma.wallenc.domain.models.IFile -class LocalFile(override val metaInfo: LocalMetaInfo) : IFile \ No newline at end of file +data class LocalFile(override val metaInfo: LocalMetaInfo) : IFile \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt index 7cc9e07..5ee8b00 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/local/entity/LocalMetaInfo.kt @@ -3,20 +3,10 @@ package com.github.nullptroma.wallenc.data.vaults.local.entity import com.github.nullptroma.wallenc.domain.models.IMetaInfo import java.time.LocalDateTime -class LocalMetaInfo : IMetaInfo { - override val name: String - override val size: Int - get() = TODO("Not yet implemented") - override val isDeleted: Boolean - get() = TODO("Not yet implemented") - override val isHidden: Boolean - get() = TODO("Not yet implemented") - override val lastModified: LocalDateTime - get() = TODO("Not yet implemented") +data class LocalMetaInfo( + override val size: Long, + override val isDeleted: Boolean, + override val isHidden: Boolean, + override val lastModified: LocalDateTime, override val path: String - get() = TODO("Not yet implemented") - - init { - name = "" - } -} \ No newline at end of file +) : IMetaInfo \ No newline at end of file diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPackage.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPackage.kt index c9c2ae9..cf572a0 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPackage.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPackage.kt @@ -1,6 +1,6 @@ package com.github.nullptroma.wallenc.domain.datatypes -open class DataPackage( +sealed class DataPackage( val data: T, val isLoading: Boolean? = false, val isError: Boolean? = false diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPage.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPage.kt index 519f79c..35ae052 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPage.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/DataPage.kt @@ -2,7 +2,9 @@ package com.github.nullptroma.wallenc.domain.datatypes class DataPage( list: List, + isLoading: Boolean? = false, + isError: Boolean? = false, val hasNext: Boolean? = false, val pageLength: Int, - val pageNumber: Int -) : DataPackage>(list) \ No newline at end of file + val pageIndex: Int +) : DataPackage>(data = list, isLoading = isLoading, isError = isError) \ No newline at end of file diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IDirectory.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IDirectory.kt index 62cacb0..28fcbd9 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IDirectory.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IDirectory.kt @@ -2,5 +2,5 @@ package com.github.nullptroma.wallenc.domain.models interface IDirectory { val metaInfo: IMetaInfo - val elementsCount: Int + val elementsCount: Int? } \ No newline at end of file diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IMetaInfo.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IMetaInfo.kt index 926be06..223be07 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IMetaInfo.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IMetaInfo.kt @@ -4,8 +4,7 @@ import java.time.LocalDateTime interface IMetaInfo { - val name: String - val size: Int + val size: Long val isDeleted: Boolean val isHidden: Boolean val lastModified: LocalDateTime diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IStorageAccessor.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IStorageAccessor.kt index 174a8ce..e845b95 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IStorageAccessor.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/models/IStorageAccessor.kt @@ -21,7 +21,7 @@ interface IStorageAccessor { * @param path Путь к директории * @return Поток файлов */ - fun getFilesFlow(path: String): Flow> + fun getFilesFlow(path: String): Flow>> suspend fun getAllDirs(): List suspend fun getDirs(path: String): List @@ -30,7 +30,7 @@ interface IStorageAccessor { * @param path Путь к директории * @return Поток директорий */ - fun getDirsFlow(path: String): Flow> + fun getDirsFlow(path: String): Flow>> suspend fun touchFile(path: String) suspend fun touchDir(path: String) diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt new file mode 100644 index 0000000..fa6586e --- /dev/null +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/StorageFileManagementUseCase.kt @@ -0,0 +1,17 @@ +package com.github.nullptroma.wallenc.domain.usecases + +import com.github.nullptroma.wallenc.domain.models.IFile +import com.github.nullptroma.wallenc.domain.models.IStorage + +class StorageFileManagementUseCase { + private var _storage: IStorage? = null + + fun setStorage(storage: IStorage) { + _storage = storage + } + + suspend fun getAllFiles(): List { + val storage = _storage ?: return listOf() + return storage.accessor.getAllFiles() + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 835efd9..e40b78b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.7.1" +jacksonModuleKotlin = "2.18.2" kotlin = "2.0.10" coreKtx = "1.15.0" junit = "4.13.2" @@ -19,12 +20,13 @@ daggerHilt = "2.52" ksp = "2.0.10-1.0.24" room = "2.6.1" retrofit = "2.11.0" -gson = "2.11.0" appcompat = "1.7.0" material = "1.12.0" runtimeAndroid = "1.7.5" [libraries] +jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" } +jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonModuleKotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinReflect" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } @@ -47,8 +49,7 @@ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = # Retrofit retrofit = { group="com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"} retrofit-converter-scalars = { group="com.squareup.retrofit2", name = "converter-scalars", version.ref = "retrofit"} -retrofit-converter-gson = { group="com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit"} -google-gson = { group="com.google.code.gson", name = "gson", version.ref = "gson"} +retrofit-converter-jackson = { group="com.squareup.retrofit2", name = "converter-jackson", version.ref = "retrofit"} androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -74,8 +75,9 @@ androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "daggerHilt" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -android-library = { id = "com.android.library", version.ref = "agp" } \ No newline at end of file +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-parcelize = { id = "kotlin-parcelize" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index ee33a63..55265ba 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -4,7 +4,7 @@ plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.dagger.hilt) alias(libs.plugins.jetbrains.kotlin.serialization) - id("kotlin-parcelize") + alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.ksp) } diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt index c1a8103..66d36b1 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt @@ -1,5 +1,6 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -7,6 +8,7 @@ import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel @@ -20,12 +22,17 @@ fun LocalVaultScreen(modifier: Modifier = Modifier, val uiState by viewModel.state.collectAsStateWithLifecycle() LazyColumn(modifier = modifier) { items(uiState.storagesList) { - Card { + Card(modifier = Modifier.clickable { + viewModel.printAllFilesToLog(it) + }) { + val available = it.isAvailable.collectAsStateWithLifecycle() + val numOfFiles = it.isAvailable.collectAsStateWithLifecycle() + val size = it.isAvailable.collectAsStateWithLifecycle() Column { Text(it.uuid.toString()) - Text("IsAvailable: ${it.isAvailable.value}") - Text("Files: ${it.numberOfFiles.value}") - Text("Size: ${it.size.value}") + Text("IsAvailable: $available") + Text("Files: $numOfFiles") + Text("Size: $size") } } } diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt index 79e84eb..e7e83e1 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt @@ -1,18 +1,24 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault import androidx.lifecycle.viewModelScope +import com.github.nullptroma.wallenc.domain.models.IStorage import com.github.nullptroma.wallenc.domain.usecases.GetAllRawStoragesUseCase +import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import com.github.nullptroma.wallenc.presentation.viewmodel.ViewModelBase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel -class LocalVaultViewModel @Inject constructor(private val getAllRawStoragesUseCase: GetAllRawStoragesUseCase) : +class LocalVaultViewModel @Inject constructor( + private val _getAllRawStoragesUseCase: GetAllRawStoragesUseCase, + private val _storageFileManagementUseCase: StorageFileManagementUseCase +) : ViewModelBase(LocalVaultScreenState(listOf())) { init { viewModelScope.launch { - getAllRawStoragesUseCase.localStorage.storages.collect { + _getAllRawStoragesUseCase.localStorage.storages.collect { val newState = state.value.copy( storagesList = it ) @@ -20,4 +26,15 @@ class LocalVaultViewModel @Inject constructor(private val getAllRawStoragesUseCa } } } + + fun printAllFilesToLog(storage: IStorage) { + _storageFileManagementUseCase.setStorage(storage) + viewModelScope.launch { + val files = _storageFileManagementUseCase.getAllFiles() + for (file in files) { + Timber.tag("File") + Timber.d(file.metaInfo.toString()) + } + } + } } \ No newline at end of file