From ab474938e0294397d3d1f0001623c2671d7916eb Mon Sep 17 00:00:00 2001 From: ShubertMunthali Date: Wed, 8 Feb 2023 15:01:22 +0200 Subject: [PATCH 01/16] Re-implementing version tracking --- app/build.gradle | 6 +- app/src/main/java/space/taran/arkmemo/App.kt | 2 +- .../space/taran/arkmemo/data/ResourceMeta.kt | 3 +- .../arkmemo/{ => data}/models/TextNote.kt | 10 +- .../taran/arkmemo/data/models/Version.kt | 8 + .../arkmemo/data/models/VersionsResult.kt | 9 + .../notes/text/TextNotesRepo.kt} | 107 +++++----- .../data/repo/versions/PlainVersionStorage.kt | 199 ++++++++++++++++++ .../data/repo/versions/VersionStorage.kt | 24 +++ .../data/repo/versions/VersionStorageRepo.kt | 23 ++ .../data/viewmodels/EditTextNotesViewModel.kt | 16 +- .../data/viewmodels/TextNotesViewModel.kt | 18 +- .../data/viewmodels/VersionsViewModel.kt | 73 +++++++ .../space/taran/arkmemo/files/FilePicker.kt | 4 +- .../taran/arkmemo/files/parsers/JsonParser.kt | 2 +- .../arkmemo/preferences/MemoPreferences.kt | 7 +- .../arkmemo/ui/activities/MainActivity.kt | 19 +- .../ui/adapters/TextNotesListAdapter.kt | 30 ++- .../ui/dialogs/NoteDeleteDialogFragment.kt | 2 +- .../ui/fragments/EditTextNotesFragment.kt | 100 ++++++--- .../arkmemo/ui/fragments/SettingsFragment.kt | 2 +- .../ui/fragments/TextNoteVersionsFragment.kt | 102 +++++++++ .../arkmemo/ui/fragments/TextNotesFragment.kt | 63 +++++- .../taran/arkmemo/ui/views/PathPreference.kt | 2 +- .../space/taran/arkmemo/utils/ArkFiles.kt | 5 + .../java/space/taran/arkmemo/utils/Config.kt | 3 +- app/src/main/res/drawable/ic_history.xml | 5 + app/src/main/res/drawable/ic_note.xml | 5 + .../res/layout/fragment_edit_text_notes.xml | 4 +- .../main/res/layout/fragment_text_notes.xml | 2 + app/src/main/res/layout/recyclerview.xml | 2 +- app/src/main/res/layout/text_note.xml | 52 +++-- app/src/main/res/values/strings.xml | 4 +- .../space/taran/arkmemo/VersionStorageTest.kt | 98 +++++++++ 34 files changed, 856 insertions(+), 155 deletions(-) rename app/src/main/java/space/taran/arkmemo/{ => data}/models/TextNote.kt (67%) create mode 100644 app/src/main/java/space/taran/arkmemo/data/models/Version.kt create mode 100644 app/src/main/java/space/taran/arkmemo/data/models/VersionsResult.kt rename app/src/main/java/space/taran/arkmemo/data/{repositories/TextNotesRepository.kt => repo/notes/text/TextNotesRepo.kt} (56%) create mode 100644 app/src/main/java/space/taran/arkmemo/data/repo/versions/PlainVersionStorage.kt create mode 100644 app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorage.kt create mode 100644 app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorageRepo.kt create mode 100644 app/src/main/java/space/taran/arkmemo/data/viewmodels/VersionsViewModel.kt create mode 100644 app/src/main/java/space/taran/arkmemo/ui/fragments/TextNoteVersionsFragment.kt create mode 100644 app/src/main/java/space/taran/arkmemo/utils/ArkFiles.kt create mode 100644 app/src/main/res/drawable/ic_history.xml create mode 100644 app/src/main/res/drawable/ic_note.xml create mode 100644 app/src/test/java/space/taran/arkmemo/VersionStorageTest.kt diff --git a/app/build.gradle b/app/build.gradle index 88cf1a46..e66347a7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,10 +84,8 @@ dependencies { implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' - //implementation 'com.github.ARK-Builders:ark-filepicker:b2bfa01ea7' - - implementation 'com.github.ark-builders:ark-filepicker:b2bfa01ea7' - implementation 'space.taran:arklib:0.1.0-SNAPSHOT-7df9a4e581' + implementation 'com.github.ark-builders:ark-filepicker:ae2c37ea87' + implementation 'space.taran:arklib:0.1.0-SNAPSHOT-cb4314df78' implementation 'ch.acra:acra-http:5.9.5' implementation 'ch.acra:acra-dialog:5.9.5' diff --git a/app/src/main/java/space/taran/arkmemo/App.kt b/app/src/main/java/space/taran/arkmemo/App.kt index 83839790..85fcd665 100644 --- a/app/src/main/java/space/taran/arkmemo/App.kt +++ b/app/src/main/java/space/taran/arkmemo/App.kt @@ -11,7 +11,7 @@ import org.acra.config.httpSender import org.acra.data.StringFormat import org.acra.ktx.initAcra import org.acra.sender.HttpSender -import space.taran.arkmemo.space.taran.arkmemo.utils.Config +import space.taran.arkmemo.utils.Config @HiltAndroidApp class App: Application() { diff --git a/app/src/main/java/space/taran/arkmemo/data/ResourceMeta.kt b/app/src/main/java/space/taran/arkmemo/data/ResourceMeta.kt index d32d62bb..92153616 100644 --- a/app/src/main/java/space/taran/arkmemo/data/ResourceMeta.kt +++ b/app/src/main/java/space/taran/arkmemo/data/ResourceMeta.kt @@ -1,9 +1,10 @@ package space.taran.arkmemo.data import java.nio.file.attribute.FileTime +import space.taran.arklib.ResourceId data class ResourceMeta( - val id: Long, + val id: ResourceId, val name: String, val extension: String, val modified: FileTime, diff --git a/app/src/main/java/space/taran/arkmemo/models/TextNote.kt b/app/src/main/java/space/taran/arkmemo/data/models/TextNote.kt similarity index 67% rename from app/src/main/java/space/taran/arkmemo/models/TextNote.kt rename to app/src/main/java/space/taran/arkmemo/data/models/TextNote.kt index 9b007f5b..d8d4acc2 100644 --- a/app/src/main/java/space/taran/arkmemo/models/TextNote.kt +++ b/app/src/main/java/space/taran/arkmemo/data/models/TextNote.kt @@ -1,4 +1,4 @@ -package space.taran.arkmemo.models +package space.taran.arkmemo.data.models import android.os.Parcel import android.os.Parcelable @@ -8,11 +8,17 @@ import kotlinx.parcelize.Parcelize @Parcelize data class TextNote ( - val content: Content, + var content: Content, @IgnoredOnParcel val meta: ResourceMeta? = null ): Parcelable { + fun putContent(content: Content) { + this.content = content + } + + fun isNotEmpty() = content.data.isNotEmpty() + @Parcelize data class Content( val title: String, diff --git a/app/src/main/java/space/taran/arkmemo/data/models/Version.kt b/app/src/main/java/space/taran/arkmemo/data/models/Version.kt new file mode 100644 index 00000000..6af2bf97 --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/data/models/Version.kt @@ -0,0 +1,8 @@ +package space.taran.arkmemo.data.models + +import space.taran.arklib.ResourceId + +data class Version( + val parent: ResourceId, + val child: ResourceId +) \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/models/VersionsResult.kt b/app/src/main/java/space/taran/arkmemo/data/models/VersionsResult.kt new file mode 100644 index 00000000..f8bacb3b --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/data/models/VersionsResult.kt @@ -0,0 +1,9 @@ +package space.taran.arkmemo.data.models + +import space.taran.arklib.ResourceId + +data class VersionsResult ( + val versions: List, + val parents: Set, + val children: List +) \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/repositories/TextNotesRepository.kt b/app/src/main/java/space/taran/arkmemo/data/repo/notes/text/TextNotesRepo.kt similarity index 56% rename from app/src/main/java/space/taran/arkmemo/data/repositories/TextNotesRepository.kt rename to app/src/main/java/space/taran/arkmemo/data/repo/notes/text/TextNotesRepo.kt index 264025f3..495001a2 100644 --- a/app/src/main/java/space/taran/arkmemo/data/repositories/TextNotesRepository.kt +++ b/app/src/main/java/space/taran/arkmemo/data/repo/notes/text/TextNotesRepo.kt @@ -1,46 +1,51 @@ -package space.taran.arkmemo.data.repositories +package space.taran.arkmemo.data.repo.notes.text import android.content.Context import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext +import space.taran.arklib.ResourceId import space.taran.arklib.computeId import space.taran.arkmemo.data.ResourceMeta import space.taran.arkmemo.files.parsers.JsonParser -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.models.TextNote +import space.taran.arkmemo.data.models.Version +import space.taran.arkmemo.data.repo.versions.VersionStorageRepo import space.taran.arkmemo.preferences.MemoPreferences import java.io.* import java.nio.file.Files import java.nio.file.Path import javax.inject.Inject -import kotlin.io.path.extension -import kotlin.io.path.getLastModifiedTime +import kotlin.io.path.* -class TextNotesRepository @Inject constructor() { +class TextNotesRepo @Inject constructor() { @Inject @ApplicationContext lateinit var context: Context - fun saveNote(note: TextNote?) { - if (note != null) { - val path = getPath() - if (path != null) { - Files.list(path) - createTextNoteFile( - path, - JsonParser.parseNoteToJson(note.content), - ) - } + fun saveNote(note: TextNote): ResourceId { + var id: ResourceId? = null + val path = MemoPreferences.getInstance(context) + .getPath() + if (path != null) { + Files.list(path) + id = createTextNoteFile( + path, + note + ) } + return id!! } fun deleteNote(note: TextNote) { - val filePath = getPath()?.resolve("${note.meta?.name}") + val filePath = MemoPreferences.getInstance(context) + .getPath()?.resolve("${note.meta?.name}") removeFileFromMemory(filePath) - Log.d("Deleted", note.meta?.name!!) + Log.d("text-notes-repo", "deleted ${note.meta?.name!!}") } fun getAllNotes(): List { val notes = mutableListOf() - val path = getPath() + val path = MemoPreferences.getInstance(context) + .getPath() var number = 0 if (path != null) { Files.list(path).forEach { filePath -> @@ -79,42 +84,44 @@ class TextNotesRepository @Inject constructor() { return notes } - private fun createTextNoteFile(path: Path?, noteString: String?) { + private fun createTextNoteFile(path: Path?, note: TextNote): ResourceId { + var id: ResourceId? = null fun writeToFile(bufferedWriter: BufferedWriter) { with(bufferedWriter) { + val noteString = JsonParser.parseNoteToJson(note.content) write(noteString) close() } } if (path != null) { val file = path.toFile() - val noteFile = File(file, "${DUMMY_FILENAME}.${NOTE_EXT}") - if (!noteFile.exists()) { - try { - val fileWriter = FileWriter(noteFile) - val bufferedWriter = BufferedWriter(fileWriter) - writeToFile(bufferedWriter) - - val id = computeId(Files.size(noteFile.toPath()), noteFile.toPath()) - - Log.d("Filename", noteFile.name) - - with(noteFile){ - val newFile = File(file, "$id.${NOTE_EXT}") - - if(!newFile.exists()) - if (renameTo(newFile)) - Log.d("New filename", newFile.name) - else - removeFileFromMemory(noteFile.toPath()) + val noteFile = File.createTempFile(FILENAME,".$NOTE_EXT") + val notePath = noteFile.toPath() + try { + val fileWriter = FileWriter(noteFile) + val bufferedWriter = BufferedWriter(fileWriter) + writeToFile(bufferedWriter) + + id = computeId(Files.size(notePath), notePath) + + Log.d("text-notes-repo/filename", notePath.name) + Log.d("text-notes-repo/file id", id.toString()) + + with(noteFile){ + val newFile = File(file, "${id.crc32}.$NOTE_EXT") + if(!newFile.exists()) + if (renameTo(newFile)) + Log.d("text-notes-repo/new filename", newFile.name) + else + removeFileFromMemory(noteFile.toPath()) - } - - } catch (e: Exception) { - e.printStackTrace() } + + } catch (e: Exception) { + e.printStackTrace() } } + return id!! } private fun removeFileFromMemory(path: Path?) { @@ -122,22 +129,8 @@ class TextNotesRepository @Inject constructor() { Files.deleteIfExists(path) } - private fun getPath(): Path? { - val prefs = MemoPreferences.getInstance(context) - val pathString = prefs.getPath() - var path: Path? = null - try { - val file = File(pathString!!) - file.mkdir() - path = file.toPath() - } catch (e: Exception) { - e.printStackTrace() - } - return path - } - companion object { private const val NOTE_EXT = "note" - private const val DUMMY_FILENAME = "Note" + private const val FILENAME = "Note" } } \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/repo/versions/PlainVersionStorage.kt b/app/src/main/java/space/taran/arkmemo/data/repo/versions/PlainVersionStorage.kt new file mode 100644 index 00000000..27450049 --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/data/repo/versions/PlainVersionStorage.kt @@ -0,0 +1,199 @@ +package space.taran.arkmemo.data.repo.versions + +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import space.taran.arklib.ResourceId +import space.taran.arklib.arkFolder +import space.taran.arkmemo.data.models.Version +import space.taran.arkmemo.data.models.VersionsResult +import space.taran.arkmemo.utils.arkVersions +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.FileTime +import kotlin.io.path.readLines +import kotlin.io.path.writeLines + +class PlainVersionStorage(private val root: Path): VersionStorage { + + private val storageFile = root.arkFolder().arkVersions() + private var lastModified = FileTime.fromMillis(0L) + private val versions = mutableListOf() + private val parents = mutableSetOf() + private val children = mutableListOf() + + + suspend fun init() = + withContext(Dispatchers.IO) { + if (Files.exists(storageFile)) { + val result = readStorage() + lastModified = Files.getLastModifiedTime(storageFile) + Log.d( + VERSIONS_STORAGE, + "file $storageFile exists," + + " last modified at $lastModified" + ) + versions.addAll(result.versions) + parents.addAll(result.parents) + children.addAll(result.children) + } else Log.d( + VERSIONS_STORAGE, + "file $storageFile doesn't exists" + ) + } + + override fun isLatestVersion(id: ResourceId) = childrenNotParents().contains(id) + + private fun replace(oldVersion: Version, newVersion: Version) { + val replaceIndex = versions.indexOf(oldVersion) + versions[replaceIndex] = newVersion + } + + override fun contains(id: ResourceId): Boolean { + return parents.contains(id) || children.contains(id) + } + + override suspend fun add(version: Version) { + versions.add(version) + parents.add(version.parent) + children.add(version.child) + persist() + } + + override suspend fun forget(id: ResourceId) { + if (!parents.contains(id)) { + val myParents = parentsTreeByChild(id) + myParents[id]?.forEach { parent -> + val version = versions + .find { it.parent == parent } + versions.remove(version) + parents.remove(version?.parent) + children.remove(version?.child) + } + } + if (parents.contains(id) && !children.contains(id)) { + val version = versions.find { + it.parent == id + } + versions.remove(version) + parents.remove(id) + } + if (parents.contains(id) && children.contains(id)) { + val versionIdIsChild = versions.find { + it.child == id + } + val versionIdIsParent = versions.find { + it.parent == id + } + val newVersion = Version( + versionIdIsChild?.parent!!, + versionIdIsParent?.child!! + ) + replace(versionIdIsChild, newVersion) + versions.remove(versionIdIsParent) + parents.remove(id) + children.remove(id) + } + persist() + } + + override fun versions() = versions + + override fun parentsTreeByChild( + child: ResourceId + ): Map> { + var localChild = child + var parent: ResourceId? + val parents = mutableListOf() + for (version in versions) { + parent = versions.find { + it.child == localChild + }?.parent + if (parent != null && children.contains(parent)) + localChild = parent + if (parent != null) parents.add(parent) + if (!children.contains(parent)) + break + } + return mapOf(child to parents) + } + + override fun childrenNotParents(): List { + return children.filter { + !parents.contains(it) + } + } + + private suspend fun writeToStorage() = + withContext(Dispatchers.IO) { + val lines = mutableListOf() + lines.add( + "$STORAGE_VERSION_PREFIX$STORAGE_VERSION" + ) + lines.addAll( + versions.map { + "${it.parent}$KEY_VALUE_SEPARATOR${it.child}" + } + ) + storageFile.writeLines(lines, Charsets.UTF_8) + } + + private suspend fun readStorage(): VersionsResult = + withContext(Dispatchers.IO) { + val lines = Files.readAllLines(storageFile) + val storageVersion = lines.removeAt(0) + verifyVersion(storageVersion) + val versions = lines.map { + val parts = it.split(KEY_VALUE_SEPARATOR) + val parent = ResourceId.fromString(parts[0]) + val child = ResourceId.fromString(parts[1]) + Log.d( + VERSIONS_STORAGE, + it + ) + Version(parent, child) + } + val parents = versions.map { + Log.d( + VERSIONS_STORAGE, + "parent: ${it.parent}" + ) + it.parent + }.toSet() + val children = versions.map { + Log.d( + VERSIONS_STORAGE, + "child: ${it.child}" + ) + it.child + } + return@withContext VersionsResult( + versions, + parents, + children + ) + } + + override suspend fun persist() = + withContext(Dispatchers.IO) { + writeToStorage() + return@withContext + } + + companion object { + private const val VERSIONS_STORAGE = "versions" + private const val STORAGE_VERSION_PREFIX = "version " + private const val STORAGE_VERSION = 1 + private const val KEY_VALUE_SEPARATOR = "->" + + private fun verifyVersion(header: String) { + if (!header.startsWith(STORAGE_VERSION_PREFIX)) + throw IllegalStateException("Unknown storage version") + val version = header.removePrefix(STORAGE_VERSION_PREFIX).toInt() + if (version > STORAGE_VERSION) + throw IllegalStateException("Storage version is newer than app") + if (version < STORAGE_VERSION) + throw IllegalStateException("Storage version is older than app") + } + } +} diff --git a/app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorage.kt b/app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorage.kt new file mode 100644 index 00000000..b4ba231a --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorage.kt @@ -0,0 +1,24 @@ +package space.taran.arkmemo.data.repo.versions + +import space.taran.arklib.ResourceId +import space.taran.arkmemo.data.models.Version + +interface VersionStorage { + suspend fun add(version: Version) + + suspend fun forget(id: ResourceId) + + fun versions(): List + + fun contains(id: ResourceId): Boolean + + fun parentsTreeByChild( + child: ResourceId + ): Map> + + fun childrenNotParents(): List + + fun isLatestVersion(id: ResourceId): Boolean + + suspend fun persist() +} \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorageRepo.kt b/app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorageRepo.kt new file mode 100644 index 00000000..c38df6e2 --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/data/repo/versions/VersionStorageRepo.kt @@ -0,0 +1,23 @@ +package space.taran.arkmemo.data.repo.versions + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import space.taran.arkmemo.preferences.MemoPreferences +import java.nio.file.Path +import javax.inject.Inject + +class VersionStorageRepo @Inject constructor() { + + @Inject @ApplicationContext lateinit var context: Context + private val storageByRoot = mutableMapOf() + + suspend fun provide(): VersionStorage { + val root = MemoPreferences.getInstance(context).getPath()!! + if (storageByRoot[root] == null) { + val versionStorage = PlainVersionStorage(root) + versionStorage.init() + storageByRoot[root] = versionStorage + } + return storageByRoot[root]!! + } +} \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/viewmodels/EditTextNotesViewModel.kt b/app/src/main/java/space/taran/arkmemo/data/viewmodels/EditTextNotesViewModel.kt index e1d74424..79dd394b 100644 --- a/app/src/main/java/space/taran/arkmemo/data/viewmodels/EditTextNotesViewModel.kt +++ b/app/src/main/java/space/taran/arkmemo/data/viewmodels/EditTextNotesViewModel.kt @@ -1,13 +1,13 @@ package space.taran.arkmemo.data.viewmodels -import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import space.taran.arkmemo.data.repositories.TextNotesRepository -import space.taran.arkmemo.models.TextNote +import space.taran.arklib.ResourceId +import space.taran.arkmemo.data.repo.notes.text.TextNotesRepo +import space.taran.arkmemo.data.models.TextNote import javax.inject.Inject @HiltViewModel @@ -15,11 +15,15 @@ class EditTextNotesViewModel @Inject constructor(): ViewModel() { private val iODispatcher = Dispatchers.IO - @Inject lateinit var repo: TextNotesRepository + @Inject lateinit var repo: TextNotesRepo - fun saveNote(note: TextNote){ + fun saveNote( + note: TextNote, + addToVersion: (TextNote, ResourceId) -> Unit + ) { viewModelScope.launch(iODispatcher) { - repo.saveNote(note) + val newNoteId = repo.saveNote(note) + addToVersion(note, newNoteId) } } } \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/viewmodels/TextNotesViewModel.kt b/app/src/main/java/space/taran/arkmemo/data/viewmodels/TextNotesViewModel.kt index cfc2492f..923aec3b 100644 --- a/app/src/main/java/space/taran/arkmemo/data/viewmodels/TextNotesViewModel.kt +++ b/app/src/main/java/space/taran/arkmemo/data/viewmodels/TextNotesViewModel.kt @@ -1,6 +1,5 @@ package space.taran.arkmemo.data.viewmodels -import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -8,15 +7,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import space.taran.arkmemo.data.repositories.TextNotesRepository -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.repo.notes.text.TextNotesRepo +import space.taran.arkmemo.data.models.TextNote import javax.inject.Inject @HiltViewModel class TextNotesViewModel @Inject constructor(): ViewModel() { - @Inject lateinit var textNotesRepo: TextNotesRepository + @Inject lateinit var textNotesRepo: TextNotesRepo private val iODispatcher = Dispatchers.IO private val textNotes: MutableStateFlow> by lazy{ MutableStateFlow(listOf()) @@ -25,14 +23,16 @@ class TextNotesViewModel @Inject constructor(): ViewModel() { fun deleteNote(note: TextNote){ viewModelScope.launch(iODispatcher) { textNotesRepo.deleteNote(note) - textNotes.value = textNotesRepo.getAllNotes() + textNotes.emit(textNotesRepo.getAllNotes()) } } - fun getAllNotes(): StateFlow>{ + suspend fun collectAllNotes(emit: suspend (List) -> Unit) { viewModelScope.launch(iODispatcher) { - textNotes.value = textNotesRepo.getAllNotes() + textNotes.value = textNotesRepo.getAllNotes() + } + textNotes.collect { + emit(it) } - return textNotes } } \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/data/viewmodels/VersionsViewModel.kt b/app/src/main/java/space/taran/arkmemo/data/viewmodels/VersionsViewModel.kt new file mode 100644 index 00000000..bdc4e340 --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/data/viewmodels/VersionsViewModel.kt @@ -0,0 +1,73 @@ +package space.taran.arkmemo.data.viewmodels + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import space.taran.arklib.ResourceId +import space.taran.arkmemo.data.models.TextNote +import space.taran.arkmemo.data.models.Version +import space.taran.arkmemo.data.repo.versions.VersionStorage +import space.taran.arkmemo.data.repo.versions.VersionStorageRepo +import javax.inject.Inject + +@HiltViewModel +class VersionsViewModel @Inject constructor(): ViewModel() { + + @Inject lateinit var versionStorageRepo: VersionStorageRepo + private lateinit var versionStorage: VersionStorage + private val latestVersion = MutableStateFlow(null) + + suspend fun init() { + versionStorage = versionStorageRepo.provide() + } + + fun emitLatestVersionNoteId(newNoteId: ResourceId) { + viewModelScope.launch { + latestVersion.emit(newNoteId) + } + } + + suspend fun collectLatestVersionNoteId( emit: suspend (ResourceId) -> Unit) { + latestVersion.collectLatest { + emit(it!!) + } + } + + fun getNoteParentsFromVersions(note: TextNote): List { + return versionStorage.parentsTreeByChild(note.meta?.id!!)[note.meta.id]!! + } + + fun addNoteToVersions(note: TextNote, newId: ResourceId) { + viewModelScope.launch(Dispatchers.IO) { + if (note.meta != null) { + val version = Version( + note.meta.id, + newId + ) + versionStorage.add(version) + } + } + } + + fun getLatestVersions() = versionStorage.childrenNotParents() + + fun isNotVersionedYet(note: TextNote) = note.meta == null || + !versionStorage.contains(note.meta.id) + + fun isVersioned(note: TextNote) = !isNotVersionedYet(note) + + fun forgetNoteFromVersions(note: TextNote) { + viewModelScope.launch { + val id = note.meta?.id!! + versionStorage.forget(id) + } + } + + fun isLatestVersion(note: TextNote) = note.meta != null && + versionStorage.isLatestVersion(note.meta.id) +} \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/files/FilePicker.kt b/app/src/main/java/space/taran/arkmemo/files/FilePicker.kt index 6681f172..0f11ce5b 100644 --- a/app/src/main/java/space/taran/arkmemo/files/FilePicker.kt +++ b/app/src/main/java/space/taran/arkmemo/files/FilePicker.kt @@ -11,8 +11,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentManager import space.taran.arkfilepicker.ArkFilePickerConfig -import space.taran.arkfilepicker.ArkFilePickerFragment -import space.taran.arkfilepicker.ArkFilePickerMode +import space.taran.arkfilepicker.presentation.filepicker.ArkFilePickerFragment +import space.taran.arkfilepicker.presentation.filepicker.ArkFilePickerMode import space.taran.arkmemo.BuildConfig import space.taran.arkmemo.R diff --git a/app/src/main/java/space/taran/arkmemo/files/parsers/JsonParser.kt b/app/src/main/java/space/taran/arkmemo/files/parsers/JsonParser.kt index 60e6668d..5e14e3e4 100644 --- a/app/src/main/java/space/taran/arkmemo/files/parsers/JsonParser.kt +++ b/app/src/main/java/space/taran/arkmemo/files/parsers/JsonParser.kt @@ -1,7 +1,7 @@ package space.taran.arkmemo.files.parsers import com.google.gson.Gson -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.models.TextNote class JsonParser { companion object{ diff --git a/app/src/main/java/space/taran/arkmemo/preferences/MemoPreferences.kt b/app/src/main/java/space/taran/arkmemo/preferences/MemoPreferences.kt index fb5ccde6..1e891857 100644 --- a/app/src/main/java/space/taran/arkmemo/preferences/MemoPreferences.kt +++ b/app/src/main/java/space/taran/arkmemo/preferences/MemoPreferences.kt @@ -2,6 +2,8 @@ package space.taran.arkmemo.preferences import android.content.Context import android.content.Context.MODE_PRIVATE +import java.nio.file.Path +import kotlin.io.path.Path class MemoPreferences private constructor(context: Context) { private val sharedPreferences = context.getSharedPreferences(NAME, MODE_PRIVATE) @@ -14,7 +16,10 @@ class MemoPreferences private constructor(context: Context) { } } - fun getPath() = sharedPreferences.getString(CURRENT_NOTES_PATH, null) + fun getPath() = sharedPreferences + .getString(CURRENT_NOTES_PATH, null)?.let { + Path(it) + } companion object{ private const val NAME = "memo_prefs" diff --git a/app/src/main/java/space/taran/arkmemo/ui/activities/MainActivity.kt b/app/src/main/java/space/taran/arkmemo/ui/activities/MainActivity.kt index 03b0f77c..922f6b42 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/activities/MainActivity.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/activities/MainActivity.kt @@ -13,7 +13,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint -import space.taran.arkfilepicker.onArkPathPicked +import space.taran.arkfilepicker.presentation.onArkPathPicked import space.taran.arkmemo.R import space.taran.arkmemo.contracts.PermissionContract import space.taran.arkmemo.databinding.ActivityMainBinding @@ -21,6 +21,7 @@ import space.taran.arkmemo.files.FilePicker import space.taran.arkmemo.preferences.MemoPreferences import space.taran.arkmemo.ui.fragments.EditTextNotesFragment import space.taran.arkmemo.ui.fragments.SettingsFragment +import space.taran.arkmemo.ui.fragments.TextNoteVersionsFragment import space.taran.arkmemo.ui.fragments.TextNotesFragment @AndroidEntryPoint @@ -56,10 +57,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { setContentView(binding.root) setSupportActionBar(binding.toolbar) binding.toolbar.setNavigationOnClickListener { - onBackPressed() + onBackPressedDispatcher.onBackPressed() } - fun showFragment(){ + fun showFragment() { val textDataFromIntent = intent?.getStringExtra(Intent.EXTRA_TEXT) if (textDataFromIntent != null) { fragment = EditTextNotesFragment.newInstance(textDataFromIntent) @@ -99,8 +100,12 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main_menu, menu) this.menu = menu - if(fragment.tag != TextNotesFragment.TAG) - showSettingsButton(false) + if( + fragment.tag == TextNotesFragment.TAG || + fragment.tag == TextNoteVersionsFragment.TAG + ) + showSettingsButton(true) + else showSettingsButton(false) return true } @@ -126,10 +131,6 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } } - fun showFragment(){ - - } - companion object{ private const val CURRENT_FRAGMENT_TAG = "current fragment tag" } diff --git a/app/src/main/java/space/taran/arkmemo/ui/adapters/TextNotesListAdapter.kt b/app/src/main/java/space/taran/arkmemo/ui/adapters/TextNotesListAdapter.kt index 1e52eb62..d770ffc7 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/adapters/TextNotesListAdapter.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/adapters/TextNotesListAdapter.kt @@ -4,21 +4,27 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import space.taran.arkmemo.R import space.taran.arkmemo.databinding.TextNoteBinding -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.models.TextNote import space.taran.arkmemo.ui.activities.MainActivity import space.taran.arkmemo.ui.activities.replaceFragment import space.taran.arkmemo.ui.dialogs.NoteDeleteDialogFragment import space.taran.arkmemo.ui.fragments.EditTextNotesFragment +import space.taran.arkmemo.ui.fragments.TextNoteVersionsFragment class TextNotesListAdapter(private val notes: List): RecyclerView.Adapter() { private var activity: MainActivity? = null private var fragmentManager: FragmentManager? = null + private var showVersionsTracker = false + var showLatestNoteIcon: (TextNote) -> Boolean = { + false + } fun setActivity(activity: AppCompatActivity){ this.activity = activity as MainActivity @@ -28,6 +34,10 @@ class TextNotesListAdapter(private val notes: List): RecyclerView.Adap fragmentManager = manager } + fun showVersionsTracker(show: Boolean) { + showVersionsTracker = show + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.text_note, parent, false) return NoteViewHolder(itemView) @@ -36,6 +46,7 @@ class TextNotesListAdapter(private val notes: List): RecyclerView.Adap override fun onBindViewHolder(holder: NoteViewHolder, position: Int) { holder.title.text = notes[position].content.title holder.date.text = notes[position].meta?.modified.toString() + holder.ivLatestNote.isVisible = showLatestNoteIcon(notes[position]) } override fun getItemCount() = notes.size @@ -47,6 +58,7 @@ class TextNotesListAdapter(private val notes: List): RecyclerView.Adap val title = binding.noteTitle val date = binding.noteDate + val ivLatestNote = binding.ivLatestNote private val clickNoteToEditListener = View.OnClickListener { val selectedNote = notes[bindingAdapterPosition] @@ -54,15 +66,27 @@ class TextNotesListAdapter(private val notes: List): RecyclerView.Adap activity?.replaceFragment(activity?.fragment!!, EditTextNotesFragment.TAG) } - private val deleteNoteClickListener = View.OnClickListener{ + private val deleteNoteClickListener = View.OnClickListener { NoteDeleteDialogFragment() .setNoteToBeDeleted(notes[bindingAdapterPosition]) .show(fragmentManager!!, NoteDeleteDialogFragment.TAG) } + private val trackVersionsListener = View.OnClickListener { + val note = notes[bindingAdapterPosition] + activity?.fragment = TextNoteVersionsFragment.newInstance( + note + ) + activity?.replaceFragment(activity?.fragment!!, TextNoteVersionsFragment.TAG) + } + init { binding.theNote.setOnClickListener(clickNoteToEditListener) - binding.deleteNote.setOnClickListener(deleteNoteClickListener) + binding.btnDelete.setOnClickListener(deleteNoteClickListener) + binding.btnTrackVersions.apply { + isVisible = showVersionsTracker + setOnClickListener(trackVersionsListener) + } } } diff --git a/app/src/main/java/space/taran/arkmemo/ui/dialogs/NoteDeleteDialogFragment.kt b/app/src/main/java/space/taran/arkmemo/ui/dialogs/NoteDeleteDialogFragment.kt index fe9c0c4d..7537e69a 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/dialogs/NoteDeleteDialogFragment.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/dialogs/NoteDeleteDialogFragment.kt @@ -6,7 +6,7 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import space.taran.arkmemo.R -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.models.TextNote import space.taran.arkmemo.ui.fragments.deleteTextNote class NoteDeleteDialogFragment: DialogFragment() { diff --git a/app/src/main/java/space/taran/arkmemo/ui/fragments/EditTextNotesFragment.kt b/app/src/main/java/space/taran/arkmemo/ui/fragments/EditTextNotesFragment.kt index 70ca2c7b..e2609b09 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/fragments/EditTextNotesFragment.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/fragments/EditTextNotesFragment.kt @@ -1,21 +1,28 @@ package space.taran.arkmemo.ui.fragments +import android.content.Context +import android.graphics.Color import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.util.Log import android.view.View +import android.view.inputmethod.InputMethodManager +import android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT import android.widget.Toast +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import space.taran.arkmemo.R import space.taran.arkmemo.data.viewmodels.EditTextNotesViewModel -import space.taran.arkmemo.data.viewmodels.TextNotesViewModel import space.taran.arkmemo.databinding.FragmentEditTextNotesBinding -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.models.TextNote +import space.taran.arkmemo.data.viewmodels.VersionsViewModel import space.taran.arkmemo.ui.activities.MainActivity @AndroidEntryPoint @@ -26,15 +33,25 @@ class EditTextNotesFragment: Fragment(R.layout.fragment_edit_text_notes) { } private val editViewModel: EditTextNotesViewModel by viewModels() + private val versionsViewModel: VersionsViewModel by activityViewModels() private val binding by viewBinding(FragmentEditTextNotesBinding::bind) - private var note: TextNote? = null + private var note = TextNote( + TextNote.Content("", "") + ) + private var noteStr: String? = null + override fun onAttach(context: Context) { + super.onAttach(context) + lifecycleScope.launch { + versionsViewModel.init() + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - var note:TextNote? = null val editTextListener = object: TextWatcher{ override fun afterTextChanged(s: Editable?) = Unit @@ -54,9 +71,7 @@ class EditTextNotesFragment: Fragment(R.layout.fragment_edit_text_notes) { title = title, data = noteString ) - note = TextNote( - content = content - ) + note.putContent(content) } } } @@ -64,32 +79,61 @@ class EditTextNotesFragment: Fragment(R.layout.fragment_edit_text_notes) { val saveNoteButton = binding.saveNote if(arguments != null) { - this.note = requireArguments().getParcelable(NOTE_KEY) - //Log.d("Note", "${this.note?.content}") + this.note = requireArguments().getParcelable(NOTE_KEY)!! noteStr = requireArguments().getString(NOTE_STRING_KEY) + if (noteStr != null) { + val title = noteStr?.split("\n")?.get(0)!! + note.putContent( + TextNote.Content( + title = title, + data = noteStr!! + ) + ) + } + } + + if ( + versionsViewModel.isVersioned(note) && + !versionsViewModel.isLatestVersion(note) + ) { + activity.title = getString(R.string.ark_memo_old_version) + editNote.isClickable = false + editNote.isFocusable = false + editNote.setBackgroundColor(Color.LTGRAY) + } else { + val inputMethodManager = requireContext() + .getSystemService(Context.INPUT_METHOD_SERVICE) + as InputMethodManager + editNote.requestFocus() + inputMethodManager.showSoftInput(editNote, SHOW_IMPLICIT) + activity.title = getString(R.string.edit_note) + editNote.addTextChangedListener(editTextListener) } - activity.title = getString(R.string.edit_note) activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) activity.showSettingsButton(false) - editNote.requestFocus() - editNote.addTextChangedListener(editTextListener) - - if(this.note != null) - editNote.setText(this.note?.content?.data!!) - - if(noteStr != null) - editNote.setText(noteStr) - - saveNoteButton.setOnClickListener { - if(note != null) { - with(editViewModel){ - saveNote(note!!) - Toast.makeText(requireContext(), getString(R.string.ark_memo_note_saved), - Toast.LENGTH_SHORT) - .show() - activity.onBackPressed() + editNote.setText(note.content.data) + + saveNoteButton.apply { + isVisible = versionsViewModel.isLatestVersion(note) || + versionsViewModel.isNotVersionedYet(note) + if (isVisible) { + setOnClickListener { + if (note.isNotEmpty()) { + with(editViewModel) { + saveNote(note) { note, newId -> + versionsViewModel.addNoteToVersions(note, newId) + versionsViewModel.emitLatestVersionNoteId(newId) + } + Toast.makeText( + requireContext(), getString(R.string.ark_memo_note_saved), + Toast.LENGTH_SHORT + ) + .show() + activity.onBackPressedDispatcher.onBackPressed() + } + } } } } @@ -97,7 +141,7 @@ class EditTextNotesFragment: Fragment(R.layout.fragment_edit_text_notes) { companion object{ - const val TAG = "Edit Text Notes" + const val TAG = "edit-text-notes-fragment" private const val NOTE_STRING_KEY = "note string" private const val NOTE_KEY = "note key" diff --git a/app/src/main/java/space/taran/arkmemo/ui/fragments/SettingsFragment.kt b/app/src/main/java/space/taran/arkmemo/ui/fragments/SettingsFragment.kt index 3f2441ae..4e58be77 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/fragments/SettingsFragment.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/fragments/SettingsFragment.kt @@ -3,7 +3,7 @@ package space.taran.arkmemo.ui.fragments import android.os.Bundle import android.view.View import androidx.preference.PreferenceFragmentCompat -import space.taran.arkfilepicker.onArkPathPicked +import space.taran.arkfilepicker.presentation.onArkPathPicked import space.taran.arkmemo.R import space.taran.arkmemo.files.FilePicker import space.taran.arkmemo.preferences.MemoPreferences diff --git a/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNoteVersionsFragment.kt b/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNoteVersionsFragment.kt new file mode 100644 index 00000000..5469e52e --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNoteVersionsFragment.kt @@ -0,0 +1,102 @@ +package space.taran.arkmemo.ui.fragments + +import android.content.Context +import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import by.kirich1409.viewbindingdelegate.viewBinding +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import space.taran.arkmemo.R +import space.taran.arkmemo.data.models.TextNote +import space.taran.arkmemo.data.viewmodels.TextNotesViewModel +import space.taran.arkmemo.data.viewmodels.VersionsViewModel +import space.taran.arkmemo.databinding.FragmentTextNotesBinding +import space.taran.arkmemo.ui.activities.MainActivity +import space.taran.arkmemo.ui.adapters.TextNotesListAdapter + +@AndroidEntryPoint +class TextNoteVersionsFragment: Fragment(R.layout.fragment_text_notes) { + + private val binding by viewBinding(FragmentTextNotesBinding::bind) + private val activity by lazy { + requireActivity() as MainActivity + } + + private val versionsViewModel: VersionsViewModel by activityViewModels() + private val textNotesViewModel: TextNotesViewModel by activityViewModels() + + private lateinit var recyclerView: RecyclerView + + override fun onAttach(context: Context) { + super.onAttach(context) + lifecycleScope.launch { + versionsViewModel.init() + } + if (arguments != null) { + val note = if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) + requireArguments().getParcelable(NOTE_KEY, TextNote::class.java) + else requireArguments().getParcelable(NOTE_KEY) + versionsViewModel.emitLatestVersionNoteId(note?.meta?.id!!) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + activity.title = getString(R.string.ark_memo_history) + activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + recyclerView = binding.include.recyclerView + lifecycleScope.launchWhenStarted { + viewLifecycleOwner.apply { + versionsViewModel.collectLatestVersionNoteId { latestNoteId -> + textNotesViewModel.collectAllNotes { notes -> + val latestNote = notes.find { it.meta?.id == latestNoteId } + val latestNoteAndItsParents = notes.filter { note -> + note.meta?.id == latestNoteId || + versionsViewModel.getNoteParentsFromVersions( + latestNote!! + ) + .contains(note.meta?.id) + } + val adapter = TextNotesListAdapter(latestNoteAndItsParents) + val layoutManager = LinearLayoutManager(requireContext()) + adapter.setActivity(activity) + adapter.setFragmentManager(childFragmentManager) + adapter.showLatestNoteIcon = { note -> + versionsViewModel.isLatestVersion(note) || + versionsViewModel.isNotVersionedYet(note) + } + recyclerView.apply { + this.adapter = adapter + this.layoutManager = layoutManager + } + } + } + } + } + } + + override fun onResume() { + super.onResume() + activity.fragment = this + } + + companion object { + const val TAG = "versions-fragment" + const val NOTE_KEY = "note key" + + fun newInstance(note: TextNote) = TextNoteVersionsFragment().apply { + arguments = Bundle().apply { + putParcelable(NOTE_KEY, note) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNotesFragment.kt b/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNotesFragment.kt index f7e49a7b..939ae364 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNotesFragment.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/fragments/TextNotesFragment.kt @@ -1,10 +1,14 @@ package space.taran.arkmemo.ui.fragments +import android.content.Context import android.os.Bundle +import android.util.Log import android.view.View import android.widget.Button import android.widget.Toast +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -14,11 +18,14 @@ import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.floatingactionbutton.FloatingActionButton import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import space.taran.arkmemo.R import space.taran.arkmemo.data.viewmodels.TextNotesViewModel import space.taran.arkmemo.databinding.FragmentTextNotesBinding -import space.taran.arkmemo.models.TextNote +import space.taran.arkmemo.data.models.TextNote +import space.taran.arkmemo.data.viewmodels.VersionsViewModel import space.taran.arkmemo.ui.activities.MainActivity import space.taran.arkmemo.ui.activities.getTextFromClipBoard import space.taran.arkmemo.ui.activities.replaceFragment @@ -32,7 +39,8 @@ class TextNotesFragment: Fragment(R.layout.fragment_text_notes) { private val activity: MainActivity by lazy { requireActivity() as MainActivity } - private val textNotesViewModel: TextNotesViewModel by viewModels() + private val textNotesViewModel: TextNotesViewModel by activityViewModels() + private val versionsViewModel: VersionsViewModel by activityViewModels() private lateinit var newNoteButton: FloatingActionButton private lateinit var pasteNoteButton: Button @@ -53,6 +61,13 @@ class TextNotesFragment: Fragment(R.layout.fragment_text_notes) { else Toast.makeText(requireContext(), getString(R.string.nothing_to_paste), Toast.LENGTH_SHORT).show() } + override fun onAttach(context: Context) { + super.onAttach(context) + lifecycleScope.launch { + versionsViewModel.init() + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView = binding.include.recyclerView @@ -60,16 +75,28 @@ class TextNotesFragment: Fragment(R.layout.fragment_text_notes) { activity.supportActionBar?.setDisplayHomeAsUpEnabled(false) newNoteButton = binding.newNote pasteNoteButton = binding.pasteNote - newNoteButton.setOnClickListener(newNoteClickListener) - pasteNoteButton.setOnClickListener(pasteNoteClickListener) + newNoteButton.apply { + isVisible = true + setOnClickListener(newNoteClickListener) + } + pasteNoteButton.apply { + isVisible = true + setOnClickListener(pasteNoteClickListener) + } lifecycleScope.launch { - viewLifecycleOwner.apply{ + viewLifecycleOwner.apply { repeatOnLifecycle(Lifecycle.State.STARTED) { - textNotesViewModel.getAllNotes().collect { - val adapter = TextNotesListAdapter(it) + textNotesViewModel.collectAllNotes { + val latestNotes = it.filter { note -> + versionsViewModel + .getLatestVersions().contains(note.meta?.id) || + versionsViewModel.isNotVersionedYet(note) + } + val adapter = TextNotesListAdapter(latestNotes) val layoutManager = LinearLayoutManager(requireContext()) adapter.setActivity(activity) adapter.setFragmentManager(childFragmentManager) + adapter.showVersionsTracker(true) recyclerView.apply { this.layoutManager = layoutManager this.adapter = adapter @@ -91,6 +118,24 @@ class TextNotesFragment: Fragment(R.layout.fragment_text_notes) { } fun Fragment.deleteTextNote(note: TextNote){ - val viewModel: TextNotesViewModel by viewModels() - viewModel.deleteNote(note) + val viewModel: TextNotesViewModel by activityViewModels() + val versionsViewModel: VersionsViewModel by activityViewModels() + if ( + versionsViewModel.isVersioned(note) && + versionsViewModel.isLatestVersion(note) + ) + lifecycleScope.launch { + viewModel.collectAllNotes { + it.filter { note1 -> + note.meta?.id == note1.meta?.id || + versionsViewModel.getNoteParentsFromVersions(note) + .contains(note1.meta?.id!!) + } + .forEach { note2 -> + viewModel.deleteNote(note2) + } + } + } + else viewModel.deleteNote(note) + versionsViewModel.forgetNoteFromVersions(note) } \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/ui/views/PathPreference.kt b/app/src/main/java/space/taran/arkmemo/ui/views/PathPreference.kt index 8aad0d23..98bca082 100644 --- a/app/src/main/java/space/taran/arkmemo/ui/views/PathPreference.kt +++ b/app/src/main/java/space/taran/arkmemo/ui/views/PathPreference.kt @@ -26,6 +26,6 @@ class PathPreference(context: Context, attrs: AttributeSet): Preference(context, super.onBindViewHolder(holder) title = holder.findViewById(R.id.title) as TextView path = holder.findViewById(R.id.pathValue) as TextView - setPath(MemoPreferences.getInstance(context).getPath()) + setPath(MemoPreferences.getInstance(context).getPath().toString()) } } \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/utils/ArkFiles.kt b/app/src/main/java/space/taran/arkmemo/utils/ArkFiles.kt new file mode 100644 index 00000000..2203af18 --- /dev/null +++ b/app/src/main/java/space/taran/arkmemo/utils/ArkFiles.kt @@ -0,0 +1,5 @@ +package space.taran.arkmemo.utils + +import java.nio.file.Path + +fun Path.arkVersions(): Path = resolve("versions") \ No newline at end of file diff --git a/app/src/main/java/space/taran/arkmemo/utils/Config.kt b/app/src/main/java/space/taran/arkmemo/utils/Config.kt index 795735a7..60d31528 100644 --- a/app/src/main/java/space/taran/arkmemo/utils/Config.kt +++ b/app/src/main/java/space/taran/arkmemo/utils/Config.kt @@ -1,7 +1,8 @@ -package space.taran.arkmemo.space.taran.arkmemo.utils +package space.taran.arkmemo.utils import android.content.Context import com.simplemobiletools.commons.helpers.BaseConfig +import space.taran.arkmemo.space.taran.arkmemo.utils.CRASH_REPORT_ENABLE class Config(context: Context) : BaseConfig(context) { companion object { diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 00000000..751e6add --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_note.xml b/app/src/main/res/drawable/ic_note.xml new file mode 100644 index 00000000..938624ee --- /dev/null +++ b/app/src/main/res/drawable/ic_note.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_edit_text_notes.xml b/app/src/main/res/layout/fragment_edit_text_notes.xml index 1644e3e7..09906f83 100644 --- a/app/src/main/res/layout/fragment_edit_text_notes.xml +++ b/app/src/main/res/layout/fragment_edit_text_notes.xml @@ -14,7 +14,8 @@ android:paddingStart="10dp" android:paddingEnd="10dp" android:ems="10" - android:focusedByDefault="true" + android:focusable="true" + android:focusableInTouchMode="true" android:gravity="start|top" android:hint="@string/start_text_note" android:importantForAutofill="no" @@ -33,6 +34,7 @@ android:layout_marginBottom="20dp" android:gravity="center" android:text="@string/save" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="@id/editNote" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> diff --git a/app/src/main/res/layout/fragment_text_notes.xml b/app/src/main/res/layout/fragment_text_notes.xml index 9553595f..729cd762 100644 --- a/app/src/main/res/layout/fragment_text_notes.xml +++ b/app/src/main/res/layout/fragment_text_notes.xml @@ -19,6 +19,7 @@ android:layout_marginBottom="20dp" android:clickable="true" android:contentDescription="@string/new_note" + android:visibility="gone" android:src="@drawable/add" />