From 5fe39bf83be7fa3590e8089532bdd6359d35ce44 Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Tue, 23 Jan 2024 00:12:03 +0100 Subject: [PATCH 1/7] feat: DRY principle - Unify NotesManager --- ...areableNotesManager.kt => NotesManager.kt} | 8 +- .../sjaindl/notesdemoapp/NotesViewModel.kt | 25 +--- .../notesdemoapp/UnshareableNotesManager.kt | 118 ------------------ 3 files changed, 11 insertions(+), 140 deletions(-) rename app/src/main/java/com/sjaindl/notesdemoapp/{ShareableNotesManager.kt => NotesManager.kt} (96%) delete mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/UnshareableNotesManager.kt diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/ShareableNotesManager.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt similarity index 96% rename from app/src/main/java/com/sjaindl/notesdemoapp/ShareableNotesManager.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt index e6d81b7..04692f3 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/ShareableNotesManager.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File -class ShareableNotesManager( +class NotesManager( private val context: Context, ): NoteAction { @@ -25,6 +25,8 @@ class ShareableNotesManager( } override fun share(note: Note) { + if (note.shareType == ShareType.Unshareable) return + val sendIntent: Intent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, "${note.title}\n${note.text}") @@ -37,8 +39,8 @@ class ShareableNotesManager( override suspend fun load(): List { val notes = readFromFiles() + readFromDatabase() - return notes.filter { - it.shareType == ShareType.Shareable + return notes.sortedBy { + it.shareType } } diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt index 1ced06a..f8849ad 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt @@ -5,46 +5,36 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.sjaindl.notesdemoapp.model.Note -import com.sjaindl.notesdemoapp.model.ShareType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class NotesViewModel( - private val shareableNotesManager: ShareableNotesManager, - private val unshareableNotesManager: UnshareableNotesManager, + private val notesManager: NotesManager, ): ViewModel() { private val _notes = MutableStateFlow>(emptyList()) val notes = _notes.asStateFlow() fun loadNotes() = viewModelScope.launch { - _notes.value = shareableNotesManager.load() + unshareableNotesManager.load() + _notes.value = notesManager.load() } fun addNote(note: Note) { viewModelScope.launch { - if (note.shareType == ShareType.Shareable) { - shareableNotesManager.save(note = note) - } else { - unshareableNotesManager.save(note = note) - } + notesManager.save(note = note) } _notes.value += note } fun deleteNote(note: Note) { viewModelScope.launch { - if (note.shareType == ShareType.Shareable) { - shareableNotesManager.delete(note = note) - } else { - unshareableNotesManager.delete(note = note) - } + notesManager.delete(note = note) } _notes.value -= note } fun share(note: Note) { - shareableNotesManager.share(note = note) + notesManager.share(note = note) } class NotesViewModelFactory( @@ -53,10 +43,7 @@ class NotesViewModel( ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { return NotesViewModel( - shareableNotesManager = ShareableNotesManager( - context = context, - ), - unshareableNotesManager = UnshareableNotesManager( + notesManager = NotesManager( context = context, ), ) as T diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/UnshareableNotesManager.kt b/app/src/main/java/com/sjaindl/notesdemoapp/UnshareableNotesManager.kt deleted file mode 100644 index 06a2a28..0000000 --- a/app/src/main/java/com/sjaindl/notesdemoapp/UnshareableNotesManager.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.sjaindl.notesdemoapp - -import android.content.Context -import androidx.room.Room -import com.sjaindl.notesdemoapp.db.AppDatabase -import com.sjaindl.notesdemoapp.db.NoteEntity -import com.sjaindl.notesdemoapp.model.Note -import com.sjaindl.notesdemoapp.model.ShareType -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import java.io.File - -class UnshareableNotesManager( - private val context: Context, -): NoteAction { - - private val database by lazy { - Room.databaseBuilder( - context = context, - klass = AppDatabase::class.java, - name = "database-notes", - ).build() - } - - override fun share(note: Note) { - // Not supported - } - - override suspend fun load(): List { - val notes = readFromFiles() + readFromDatabase() - return notes.filter { - it.shareType == ShareType.Unshareable - } - } - - override suspend fun save(note: Note) { - if (note is Note.DatabaseNote) { - saveToDatabase(note = note) - } else { - saveToFile(note = note) - } - } - - override suspend fun delete(note: Note) { - if (note is Note.DatabaseNote) { - deleteFromDatabase(note = note) - } else { - deleteFromFile(noteId = note.id) - } - } - - private fun saveToFile(note: Note) { - val fileNote = note as? Note.FileNote ?: return - - val dir = context.getDir("notes", Context.MODE_PRIVATE) - val filename = "${note.id}.json" - val fileContent = Json.encodeToString(fileNote) - val file = File(dir, filename) - - file.outputStream().use { - it.write(fileContent.toByteArray()) - } - } - - private suspend fun saveToDatabase(note: Note) { - val entity = NoteEntity( - note.id, - note.shareType, - note.title, - note.text, - ) - - database.notesDao().insertAll(entity) - } - - private fun deleteFromFile(noteId: String) { - val dir = context.getDir("notes", Context.MODE_PRIVATE) - val filename = "${noteId}.json" - val file = File(dir, filename) - file.delete() - } - - private suspend fun deleteFromDatabase(note: Note) { - val entity = NoteEntity( - id = note.id, - type = note.shareType, - title = note.title, - text = note.text, - ) - - database.notesDao().delete(entity) - } - - private fun readFromFiles(): List { - val files = context.getDir("notes", Context.MODE_PRIVATE).listFiles() - - return files?.map { file -> - val json = file.bufferedReader().useLines { lines -> - lines.fold("") { some, text -> - "$some\n$text" - } - } - - Json.decodeFromString(json) - } ?: emptyList() - } - - private suspend fun readFromDatabase(): List { - return database.notesDao().getAll().map { - Note.DatabaseNote( - id = it.id, - shareType = it.type, - title = it.title, - text = it.text, - ) - } - } -} From 454822fb5520bf221ff19c8a975cb9d972640586 Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Tue, 23 Jan 2024 00:51:23 +0100 Subject: [PATCH 2/7] feat: SOLID principles - Single Responsibility --- .../com/sjaindl/notesdemoapp/NotesManager.kt | 99 +-------------- .../sjaindl/notesdemoapp/NotesPersistence.kt | 117 ++++++++++++++++++ .../sjaindl/notesdemoapp/NotesViewModel.kt | 10 +- 3 files changed, 128 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt index 04692f3..65c6295 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt @@ -3,27 +3,13 @@ package com.sjaindl.notesdemoapp import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat -import androidx.room.Room -import com.sjaindl.notesdemoapp.db.AppDatabase -import com.sjaindl.notesdemoapp.db.NoteEntity import com.sjaindl.notesdemoapp.model.Note import com.sjaindl.notesdemoapp.model.ShareType -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import java.io.File class NotesManager( private val context: Context, ): NoteAction { - private val database by lazy { - Room.databaseBuilder( - context = context, - klass = AppDatabase::class.java, - name = "database-notes", - ).build() - } - override fun share(note: Note) { if (note.shareType == ShareType.Unshareable) return @@ -38,92 +24,15 @@ class NotesManager( } override suspend fun load(): List { - val notes = readFromFiles() + readFromDatabase() - return notes.sortedBy { - it.shareType - } + // No op + return emptyList() } override suspend fun save(note: Note) { - if (note is Note.DatabaseNote) { - saveToDatabase(note = note) - } else { - saveToFile(note = note) - } + // No op } override suspend fun delete(note: Note) { - if (note is Note.DatabaseNote) { - deleteFromDatabase(note = note) - } else { - deleteFromFile(noteId = note.id) - } - } - - private fun saveToFile(note: Note) { - val fileNote = note as? Note.FileNote ?: return - - val dir = context.getDir("notes", Context.MODE_PRIVATE) - val filename = "${note.id}.json" - val fileContent = Json.encodeToString(fileNote) - val file = File(dir, filename) - - file.outputStream().use { - it.write(fileContent.toByteArray()) - } - } - - private suspend fun saveToDatabase(note: Note) { - val entity = NoteEntity( - note.id, - note.shareType, - note.title, - note.text, - ) - - database.notesDao().insertAll(entity) - } - - private fun deleteFromFile(noteId: String) { - val dir = context.getDir("notes", Context.MODE_PRIVATE) - val filename = "${noteId}.json" - val file = File(dir, filename) - file.delete() - } - - private suspend fun deleteFromDatabase(note: Note) { - val entity = NoteEntity( - id = note.id, - type = note.shareType, - title = note.title, - text = note.text, - ) - - database.notesDao().delete(entity) - } - - private fun readFromFiles(): List { - val files = context.getDir("notes", Context.MODE_PRIVATE).listFiles() - - return files?.map { file -> - val json = file.bufferedReader().useLines { lines -> - lines.fold("") { some, text -> - "$some\n$text" - } - } - - Json.decodeFromString(json) - } ?: emptyList() - } - - private suspend fun readFromDatabase(): List { - return database.notesDao().getAll().map { - Note.DatabaseNote( - id = it.id, - shareType = it.type, - title = it.title, - text = it.text, - ) - } + // No op } } diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt new file mode 100644 index 0000000..df891e3 --- /dev/null +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt @@ -0,0 +1,117 @@ +package com.sjaindl.notesdemoapp + +import android.content.Context +import androidx.room.Room +import com.sjaindl.notesdemoapp.db.AppDatabase +import com.sjaindl.notesdemoapp.db.NoteEntity +import com.sjaindl.notesdemoapp.model.Note +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +class NotesPersistence( + private val context: Context, +): NoteAction { + + private val database by lazy { + Room.databaseBuilder( + context = context, + klass = AppDatabase::class.java, + name = "database-notes", + ).build() + } + + override fun share(note: Note) { + // No Op + } + + override suspend fun load(): List { + val notes = readFromFiles() + readFromDatabase() + return notes.sortedBy { + it.shareType + } + } + + override suspend fun save(note: Note) { + if (note is Note.DatabaseNote) { + saveToDatabase(note = note) + } else { + saveToFile(note = note) + } + } + + override suspend fun delete(note: Note) { + if (note is Note.DatabaseNote) { + deleteFromDatabase(note = note) + } else { + deleteFromFile(noteId = note.id) + } + } + + private fun saveToFile(note: Note) { + val fileNote = note as? Note.FileNote ?: return + + val dir = context.getDir("notes", Context.MODE_PRIVATE) + val filename = "${note.id}.json" + val fileContent = Json.encodeToString(fileNote) + val file = File(dir, filename) + + file.outputStream().use { + it.write(fileContent.toByteArray()) + } + } + + private suspend fun saveToDatabase(note: Note) { + val entity = NoteEntity( + note.id, + note.shareType, + note.title, + note.text, + ) + + database.notesDao().insertAll(entity) + } + + private fun deleteFromFile(noteId: String) { + val dir = context.getDir("notes", Context.MODE_PRIVATE) + val filename = "${noteId}.json" + val file = File(dir, filename) + file.delete() + } + + private suspend fun deleteFromDatabase(note: Note) { + val entity = NoteEntity( + id = note.id, + type = note.shareType, + title = note.title, + text = note.text, + ) + + database.notesDao().delete(entity) + } + + private fun readFromFiles(): List { + val files = context.getDir("notes", Context.MODE_PRIVATE).listFiles() + + return files?.map { file -> + val json = file.bufferedReader().useLines { lines -> + lines.fold("") { some, text -> + "$some\n$text" + } + } + + Json.decodeFromString(json) + } ?: emptyList() + } + + private suspend fun readFromDatabase(): List { + return database.notesDao().getAll().map { + Note.DatabaseNote( + id = it.id, + shareType = it.type, + title = it.title, + text = it.text, + ) + } + } +} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt index f8849ad..a5eb1ba 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt @@ -11,24 +11,25 @@ import kotlinx.coroutines.launch class NotesViewModel( private val notesManager: NotesManager, + private val notesPersistence: NotesPersistence, ): ViewModel() { private val _notes = MutableStateFlow>(emptyList()) val notes = _notes.asStateFlow() fun loadNotes() = viewModelScope.launch { - _notes.value = notesManager.load() + _notes.value = notesPersistence.load() } fun addNote(note: Note) { viewModelScope.launch { - notesManager.save(note = note) + notesPersistence.save(note = note) } _notes.value += note } fun deleteNote(note: Note) { viewModelScope.launch { - notesManager.delete(note = note) + notesPersistence.delete(note = note) } _notes.value -= note } @@ -46,6 +47,9 @@ class NotesViewModel( notesManager = NotesManager( context = context, ), + notesPersistence = NotesPersistence( + context = context, + ) ) as T } } From 51b169cb0824ca88ccd413bb40ee772d40686998 Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Tue, 23 Jan 2024 17:23:48 +0100 Subject: [PATCH 3/7] feat: SOLID principles - Open/Closed principle --- .../sjaindl/notesdemoapp/NotesPersistence.kt | 117 ------------------ .../sjaindl/notesdemoapp/NotesViewModel.kt | 25 +++- .../persistence/NotesDatabasePersistence.kt | 72 +++++++++++ .../persistence/NotesFilePersistence.kt | 65 ++++++++++ .../persistence/NotesPersistence.kt | 17 +++ 5 files changed, 173 insertions(+), 123 deletions(-) delete mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt create mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt create mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt create mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt deleted file mode 100644 index df891e3..0000000 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesPersistence.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.sjaindl.notesdemoapp - -import android.content.Context -import androidx.room.Room -import com.sjaindl.notesdemoapp.db.AppDatabase -import com.sjaindl.notesdemoapp.db.NoteEntity -import com.sjaindl.notesdemoapp.model.Note -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import java.io.File - -class NotesPersistence( - private val context: Context, -): NoteAction { - - private val database by lazy { - Room.databaseBuilder( - context = context, - klass = AppDatabase::class.java, - name = "database-notes", - ).build() - } - - override fun share(note: Note) { - // No Op - } - - override suspend fun load(): List { - val notes = readFromFiles() + readFromDatabase() - return notes.sortedBy { - it.shareType - } - } - - override suspend fun save(note: Note) { - if (note is Note.DatabaseNote) { - saveToDatabase(note = note) - } else { - saveToFile(note = note) - } - } - - override suspend fun delete(note: Note) { - if (note is Note.DatabaseNote) { - deleteFromDatabase(note = note) - } else { - deleteFromFile(noteId = note.id) - } - } - - private fun saveToFile(note: Note) { - val fileNote = note as? Note.FileNote ?: return - - val dir = context.getDir("notes", Context.MODE_PRIVATE) - val filename = "${note.id}.json" - val fileContent = Json.encodeToString(fileNote) - val file = File(dir, filename) - - file.outputStream().use { - it.write(fileContent.toByteArray()) - } - } - - private suspend fun saveToDatabase(note: Note) { - val entity = NoteEntity( - note.id, - note.shareType, - note.title, - note.text, - ) - - database.notesDao().insertAll(entity) - } - - private fun deleteFromFile(noteId: String) { - val dir = context.getDir("notes", Context.MODE_PRIVATE) - val filename = "${noteId}.json" - val file = File(dir, filename) - file.delete() - } - - private suspend fun deleteFromDatabase(note: Note) { - val entity = NoteEntity( - id = note.id, - type = note.shareType, - title = note.title, - text = note.text, - ) - - database.notesDao().delete(entity) - } - - private fun readFromFiles(): List { - val files = context.getDir("notes", Context.MODE_PRIVATE).listFiles() - - return files?.map { file -> - val json = file.bufferedReader().useLines { lines -> - lines.fold("") { some, text -> - "$some\n$text" - } - } - - Json.decodeFromString(json) - } ?: emptyList() - } - - private suspend fun readFromDatabase(): List { - return database.notesDao().getAll().map { - Note.DatabaseNote( - id = it.id, - shareType = it.type, - title = it.title, - text = it.text, - ) - } - } -} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt index a5eb1ba..4638726 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt @@ -5,31 +5,41 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.sjaindl.notesdemoapp.model.Note +import com.sjaindl.notesdemoapp.persistence.NotesDatabasePersistence +import com.sjaindl.notesdemoapp.persistence.NotesFilePersistence +import com.sjaindl.notesdemoapp.persistence.NotesPersistence import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class NotesViewModel( private val notesManager: NotesManager, - private val notesPersistence: NotesPersistence, + private val filePersistence: NotesPersistence, + private val databasePersistence: NotesPersistence, ): ViewModel() { private val _notes = MutableStateFlow>(emptyList()) val notes = _notes.asStateFlow() fun loadNotes() = viewModelScope.launch { - _notes.value = notesPersistence.load() + _notes.value = filePersistence.load() + databasePersistence.load() } fun addNote(note: Note) { viewModelScope.launch { - notesPersistence.save(note = note) + when(note) { + is Note.FileNote -> filePersistence.save(note = note) + is Note.DatabaseNote -> databasePersistence.save(note = note) + } } _notes.value += note } fun deleteNote(note: Note) { viewModelScope.launch { - notesPersistence.delete(note = note) + when(note) { + is Note.FileNote -> filePersistence.delete(note = note) + is Note.DatabaseNote -> databasePersistence.delete(note = note) + } } _notes.value -= note } @@ -47,9 +57,12 @@ class NotesViewModel( notesManager = NotesManager( context = context, ), - notesPersistence = NotesPersistence( + filePersistence = NotesFilePersistence( context = context, - ) + ), + databasePersistence = NotesDatabasePersistence( + context = context, + ), ) as T } } diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt new file mode 100644 index 0000000..ca304fa --- /dev/null +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt @@ -0,0 +1,72 @@ +package com.sjaindl.notesdemoapp.persistence + +import android.content.Context +import androidx.room.Room +import com.sjaindl.notesdemoapp.db.AppDatabase +import com.sjaindl.notesdemoapp.db.NoteEntity +import com.sjaindl.notesdemoapp.model.Note + +class NotesDatabasePersistence( + private val context: Context, +): NotesPersistence { + + private val database by lazy { + Room.databaseBuilder( + context = context, + klass = AppDatabase::class.java, + name = "database-notes", + ).build() + } + + override suspend fun load(): List { + val notes = read() + return notes.sortedBy { + it.shareType + } + } + + override suspend fun save(note: Note) { + if (note is Note.DatabaseNote) { + saveNote(note = note) + } + } + + override suspend fun delete(note: Note) { + if (note is Note.DatabaseNote) { + deleteNote(note = note) + } + } + + private suspend fun saveNote(note: Note) { + val entity = NoteEntity( + note.id, + note.shareType, + note.title, + note.text, + ) + + database.notesDao().insertAll(entity) + } + + private suspend fun deleteNote(note: Note) { + val entity = NoteEntity( + id = note.id, + type = note.shareType, + title = note.title, + text = note.text, + ) + + database.notesDao().delete(entity) + } + + private suspend fun read(): List { + return database.notesDao().getAll().map { + Note.DatabaseNote( + id = it.id, + shareType = it.type, + title = it.title, + text = it.text, + ) + } + } +} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt new file mode 100644 index 0000000..caf7d60 --- /dev/null +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt @@ -0,0 +1,65 @@ +package com.sjaindl.notesdemoapp.persistence + +import android.content.Context +import com.sjaindl.notesdemoapp.model.Note +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +class NotesFilePersistence( + private val context: Context, +): NotesPersistence { + + override suspend fun load(): List { + val notes = read() + return notes.sortedBy { + it.shareType + } + } + + override suspend fun save(note: Note) { + if (note is Note.FileNote) { + saveNote(note = note) + } + } + + override suspend fun delete(note: Note) { + if (note is Note.FileNote) { + delete(noteId = note.id) + } + } + + private fun saveNote(note: Note) { + val fileNote = note as? Note.FileNote ?: return + + val dir = context.getDir("notes", Context.MODE_PRIVATE) + val filename = "${note.id}.json" + val fileContent = Json.encodeToString(fileNote) + val file = File(dir, filename) + + file.outputStream().use { + it.write(fileContent.toByteArray()) + } + } + + private fun delete(noteId: String) { + val dir = context.getDir("notes", Context.MODE_PRIVATE) + val filename = "${noteId}.json" + val file = File(dir, filename) + file.delete() + } + + private fun read(): List { + val files = context.getDir("notes", Context.MODE_PRIVATE).listFiles() + + return files?.map { file -> + val json = file.bufferedReader().useLines { lines -> + lines.fold("") { some, text -> + "$some\n$text" + } + } + + Json.decodeFromString(json) + } ?: emptyList() + } +} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt new file mode 100644 index 0000000..0fd5d46 --- /dev/null +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt @@ -0,0 +1,17 @@ +package com.sjaindl.notesdemoapp.persistence + +import com.sjaindl.notesdemoapp.NoteAction +import com.sjaindl.notesdemoapp.model.Note + +interface NotesPersistence: NoteAction { + + override fun share(note: Note) { + // No op + } + + override suspend fun load(): List + + override suspend fun save(note: Note) + + override suspend fun delete(note: Note) +} From 14656ee1350f1273352107a19b335ac0e65885f7 Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Wed, 24 Jan 2024 20:38:37 +0100 Subject: [PATCH 4/7] feat: SOLID principles - Liskov substitution principle --- .../com/sjaindl/notesdemoapp/model/Note.kt | 18 ++++++++++++++++++ .../persistence/NotesDatabasePersistence.kt | 17 ++++++++--------- .../persistence/NotesFilePersistence.kt | 19 ++++++++----------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/model/Note.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/Note.kt index 255c8ad..6ab9fef 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/model/Note.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/Note.kt @@ -1,6 +1,8 @@ package com.sjaindl.notesdemoapp.model import kotlinx.serialization.Serializable +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract @Serializable sealed interface Note { @@ -25,3 +27,19 @@ sealed interface Note { override val text: String, ) : Note } + +@OptIn(ExperimentalContracts::class) +fun Note.isFileNote(): Boolean { + contract { + returns(true) implies (this@isFileNote is Note.FileNote) + } + return this is Note.FileNote +} + +@OptIn(ExperimentalContracts::class) +fun Note.isDatabaseNote(): Boolean { + contract { + returns(true) implies (this@isDatabaseNote is Note.DatabaseNote) + } + return this is Note.DatabaseNote +} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt index ca304fa..1732149 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt @@ -5,6 +5,7 @@ import androidx.room.Room import com.sjaindl.notesdemoapp.db.AppDatabase import com.sjaindl.notesdemoapp.db.NoteEntity import com.sjaindl.notesdemoapp.model.Note +import com.sjaindl.notesdemoapp.model.Note.DatabaseNote class NotesDatabasePersistence( private val context: Context, @@ -26,18 +27,16 @@ class NotesDatabasePersistence( } override suspend fun save(note: Note) { - if (note is Note.DatabaseNote) { - saveNote(note = note) - } + require(note is DatabaseNote) + saveNote(note = note) } override suspend fun delete(note: Note) { - if (note is Note.DatabaseNote) { - deleteNote(note = note) - } + require(note is DatabaseNote) + deleteNote(note = note) } - private suspend fun saveNote(note: Note) { + private suspend fun saveNote(note: DatabaseNote) { val entity = NoteEntity( note.id, note.shareType, @@ -48,7 +47,7 @@ class NotesDatabasePersistence( database.notesDao().insertAll(entity) } - private suspend fun deleteNote(note: Note) { + private suspend fun deleteNote(note: DatabaseNote) { val entity = NoteEntity( id = note.id, type = note.shareType, @@ -61,7 +60,7 @@ class NotesDatabasePersistence( private suspend fun read(): List { return database.notesDao().getAll().map { - Note.DatabaseNote( + DatabaseNote( id = it.id, shareType = it.type, title = it.title, diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt index caf7d60..2997962 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt @@ -2,6 +2,7 @@ package com.sjaindl.notesdemoapp.persistence import android.content.Context import com.sjaindl.notesdemoapp.model.Note +import com.sjaindl.notesdemoapp.model.Note.FileNote import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File @@ -18,23 +19,19 @@ class NotesFilePersistence( } override suspend fun save(note: Note) { - if (note is Note.FileNote) { - saveNote(note = note) - } + require(note is FileNote) + saveNote(note = note) } override suspend fun delete(note: Note) { - if (note is Note.FileNote) { - delete(noteId = note.id) - } + require(note is FileNote) + delete(noteId = note.id) } - private fun saveNote(note: Note) { - val fileNote = note as? Note.FileNote ?: return - + private fun saveNote(note: FileNote) { val dir = context.getDir("notes", Context.MODE_PRIVATE) val filename = "${note.id}.json" - val fileContent = Json.encodeToString(fileNote) + val fileContent = Json.encodeToString(note) val file = File(dir, filename) file.outputStream().use { @@ -49,7 +46,7 @@ class NotesFilePersistence( file.delete() } - private fun read(): List { + private fun read(): List { val files = context.getDir("notes", Context.MODE_PRIVATE).listFiles() return files?.map { file -> From aefc06e480eedd92595ddabc88517984f8b9ddba Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Wed, 24 Jan 2024 21:13:36 +0100 Subject: [PATCH 5/7] feat: SOLID principles - interface segregation principle --- .../{NoteAction.kt => NotePersistenceAction.kt} | 3 +-- .../com/sjaindl/notesdemoapp/NoteShareAction.kt | 7 +++++++ .../java/com/sjaindl/notesdemoapp/NotesManager.kt | 15 +-------------- .../notesdemoapp/persistence/NotesPersistence.kt | 14 +++++--------- 4 files changed, 14 insertions(+), 25 deletions(-) rename app/src/main/java/com/sjaindl/notesdemoapp/{NoteAction.kt => NotePersistenceAction.kt} (78%) create mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NoteAction.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt similarity index 78% rename from app/src/main/java/com/sjaindl/notesdemoapp/NoteAction.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt index a6eb256..278e473 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NoteAction.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt @@ -2,8 +2,7 @@ package com.sjaindl.notesdemoapp import com.sjaindl.notesdemoapp.model.Note -interface NoteAction { - fun share(note: Note) +interface NotePersistenceAction { suspend fun load(): List suspend fun save(note: Note) suspend fun delete(note: Note) diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt new file mode 100644 index 0000000..dacba82 --- /dev/null +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt @@ -0,0 +1,7 @@ +package com.sjaindl.notesdemoapp + +import com.sjaindl.notesdemoapp.model.Note + +interface NoteShareAction { + fun share(note: Note) +} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt index 65c6295..45ab53d 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt @@ -8,7 +8,7 @@ import com.sjaindl.notesdemoapp.model.ShareType class NotesManager( private val context: Context, -): NoteAction { +): NoteShareAction { override fun share(note: Note) { if (note.shareType == ShareType.Unshareable) return @@ -22,17 +22,4 @@ class NotesManager( val shareIntent = Intent.createChooser(sendIntent, null) ContextCompat.startActivity(context, shareIntent, null) } - - override suspend fun load(): List { - // No op - return emptyList() - } - - override suspend fun save(note: Note) { - // No op - } - - override suspend fun delete(note: Note) { - // No op - } } diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt index 0fd5d46..7235162 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt @@ -1,17 +1,13 @@ package com.sjaindl.notesdemoapp.persistence -import com.sjaindl.notesdemoapp.NoteAction +import com.sjaindl.notesdemoapp.NotePersistenceAction import com.sjaindl.notesdemoapp.model.Note -interface NotesPersistence: NoteAction { +interface NotesPersistence { - override fun share(note: Note) { - // No op - } + suspend fun load(): List - override suspend fun load(): List + suspend fun save(note: Note) - override suspend fun save(note: Note) - - override suspend fun delete(note: Note) + suspend fun delete(note: Note) } From 56aa0342d4357d47c991d0995d4ebe9fe9042cae Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Wed, 24 Jan 2024 21:35:43 +0100 Subject: [PATCH 6/7] feat: Dependency Injection --- .../java/com/sjaindl/notesdemoapp/NotesViewModel.kt | 12 +++++++++++- .../persistence/NotesDatabasePersistence.kt | 12 +----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt index 4638726..49297d7 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt @@ -4,6 +4,8 @@ import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.room.Room +import com.sjaindl.notesdemoapp.db.AppDatabase import com.sjaindl.notesdemoapp.model.Note import com.sjaindl.notesdemoapp.persistence.NotesDatabasePersistence import com.sjaindl.notesdemoapp.persistence.NotesFilePersistence @@ -53,6 +55,14 @@ class NotesViewModel( ) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { + val database by lazy { + Room.databaseBuilder( + context = context, + klass = AppDatabase::class.java, + name = "database-notes", + ).build() + } + return NotesViewModel( notesManager = NotesManager( context = context, @@ -61,7 +71,7 @@ class NotesViewModel( context = context, ), databasePersistence = NotesDatabasePersistence( - context = context, + database = database, ), ) as T } diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt index 1732149..2a2fb32 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt @@ -1,24 +1,14 @@ package com.sjaindl.notesdemoapp.persistence -import android.content.Context -import androidx.room.Room import com.sjaindl.notesdemoapp.db.AppDatabase import com.sjaindl.notesdemoapp.db.NoteEntity import com.sjaindl.notesdemoapp.model.Note import com.sjaindl.notesdemoapp.model.Note.DatabaseNote class NotesDatabasePersistence( - private val context: Context, + private val database: AppDatabase, ): NotesPersistence { - private val database by lazy { - Room.databaseBuilder( - context = context, - klass = AppDatabase::class.java, - name = "database-notes", - ).build() - } - override suspend fun load(): List { val notes = read() return notes.sortedBy { From b19294b01985ac2ab3958f8f0b8b15b61a0fa0ea Mon Sep 17 00:00:00 2001 From: Stefan Jaindl Date: Wed, 8 May 2024 17:43:48 +0200 Subject: [PATCH 7/7] feat: Patterns - MVC --- app/src/main/AndroidManifest.xml | 2 +- .../com/sjaindl/notesdemoapp/MainActivity.kt | 26 ---- .../notesdemoapp/NotePersistenceAction.kt | 9 -- .../sjaindl/notesdemoapp/NotesViewModel.kt | 79 ------------ .../notesdemoapp/controller/MainActivity.kt | 112 ++++++++++++++++++ .../{ => model}/db/AppDatabase.kt | 2 +- .../notesdemoapp/{ => model}/db/NoteEntity.kt | 2 +- .../notesdemoapp/{ => model}/db/NotesDao.kt | 2 +- .../persistence/NotesDatabasePersistence.kt | 6 +- .../persistence/NotesFilePersistence.kt | 2 +- .../persistence/NotesPersistence.kt | 3 +- .../{ => model/sharing}/NoteShareAction.kt | 2 +- .../{ => model/sharing}/NotesManager.kt | 2 +- .../notesdemoapp/{ => view}/AddNoteScreen.kt | 3 +- .../notesdemoapp/{ => view}/NotesAppBar.kt | 5 +- .../notesdemoapp/{ => view}/NotesScreen.kt | 50 +++++--- .../notesdemoapp/{ => view}/SingleNote.kt | 4 +- .../notesdemoapp/{ui => view}/theme/Color.kt | 2 +- .../notesdemoapp/{ui => view}/theme/Theme.kt | 2 +- .../notesdemoapp/{ui => view}/theme/Type.kt | 2 +- 20 files changed, 167 insertions(+), 150 deletions(-) delete mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/MainActivity.kt delete mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt delete mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt create mode 100644 app/src/main/java/com/sjaindl/notesdemoapp/controller/MainActivity.kt rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model}/db/AppDatabase.kt (83%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model}/db/NoteEntity.kt (85%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model}/db/NotesDao.kt (88%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model}/persistence/NotesDatabasePersistence.kt (89%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model}/persistence/NotesFilePersistence.kt (97%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model}/persistence/NotesPersistence.kt (64%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model/sharing}/NoteShareAction.kt (68%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => model/sharing}/NotesManager.kt (93%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => view}/AddNoteScreen.kt (98%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => view}/NotesAppBar.kt (91%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => view}/NotesScreen.kt (67%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ => view}/SingleNote.kt (97%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ui => view}/theme/Color.kt (84%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ui => view}/theme/Theme.kt (98%) rename app/src/main/java/com/sjaindl/notesdemoapp/{ui => view}/theme/Type.kt (95%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ecbcabf..7326790 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ android:theme="@style/Theme.NotesDemoApp" tools:targetApi="31"> diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/MainActivity.kt b/app/src/main/java/com/sjaindl/notesdemoapp/MainActivity.kt deleted file mode 100644 index f91cf92..0000000 --- a/app/src/main/java/com/sjaindl/notesdemoapp/MainActivity.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.sjaindl.notesdemoapp - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.ui.Modifier -import com.sjaindl.notesdemoapp.ui.theme.NotesDemoAppTheme - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - NotesDemoAppTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - NotesScreen() - } - } - } - } -} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt deleted file mode 100644 index 278e473..0000000 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotePersistenceAction.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.sjaindl.notesdemoapp - -import com.sjaindl.notesdemoapp.model.Note - -interface NotePersistenceAction { - suspend fun load(): List - suspend fun save(note: Note) - suspend fun delete(note: Note) -} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt b/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt deleted file mode 100644 index 49297d7..0000000 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesViewModel.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.sjaindl.notesdemoapp - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import androidx.room.Room -import com.sjaindl.notesdemoapp.db.AppDatabase -import com.sjaindl.notesdemoapp.model.Note -import com.sjaindl.notesdemoapp.persistence.NotesDatabasePersistence -import com.sjaindl.notesdemoapp.persistence.NotesFilePersistence -import com.sjaindl.notesdemoapp.persistence.NotesPersistence -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch - -class NotesViewModel( - private val notesManager: NotesManager, - private val filePersistence: NotesPersistence, - private val databasePersistence: NotesPersistence, -): ViewModel() { - private val _notes = MutableStateFlow>(emptyList()) - val notes = _notes.asStateFlow() - - fun loadNotes() = viewModelScope.launch { - _notes.value = filePersistence.load() + databasePersistence.load() - } - - fun addNote(note: Note) { - viewModelScope.launch { - when(note) { - is Note.FileNote -> filePersistence.save(note = note) - is Note.DatabaseNote -> databasePersistence.save(note = note) - } - } - _notes.value += note - } - - fun deleteNote(note: Note) { - viewModelScope.launch { - when(note) { - is Note.FileNote -> filePersistence.delete(note = note) - is Note.DatabaseNote -> databasePersistence.delete(note = note) - } - } - _notes.value -= note - } - - fun share(note: Note) { - notesManager.share(note = note) - } - - class NotesViewModelFactory( - private val context: Context, - ) : - ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T { - val database by lazy { - Room.databaseBuilder( - context = context, - klass = AppDatabase::class.java, - name = "database-notes", - ).build() - } - - return NotesViewModel( - notesManager = NotesManager( - context = context, - ), - filePersistence = NotesFilePersistence( - context = context, - ), - databasePersistence = NotesDatabasePersistence( - database = database, - ), - ) as T - } - } -} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/controller/MainActivity.kt b/app/src/main/java/com/sjaindl/notesdemoapp/controller/MainActivity.kt new file mode 100644 index 0000000..9b310e8 --- /dev/null +++ b/app/src/main/java/com/sjaindl/notesdemoapp/controller/MainActivity.kt @@ -0,0 +1,112 @@ +package com.sjaindl.notesdemoapp.controller + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope +import androidx.room.Room +import com.sjaindl.notesdemoapp.model.db.AppDatabase +import com.sjaindl.notesdemoapp.model.Note +import com.sjaindl.notesdemoapp.model.persistence.NotesDatabasePersistence +import com.sjaindl.notesdemoapp.model.persistence.NotesFilePersistence +import com.sjaindl.notesdemoapp.model.sharing.NotesManager +import com.sjaindl.notesdemoapp.view.theme.NotesDemoAppTheme +import com.sjaindl.notesdemoapp.view.NotesScreen +import kotlinx.coroutines.launch + +class MainActivity : ComponentActivity() { + private val database by lazy { + Room.databaseBuilder( + context = this, + klass = AppDatabase::class.java, + name = "database-notes", + ).build() + } + + private val notesManager by lazy { + NotesManager( + context = this, + ) + } + + private val filePersistence by lazy { + NotesFilePersistence( + context = this, + ) + } + + private val databasePersistence by lazy { + NotesDatabasePersistence( + database = database, + ) + } + + private var notes: MutableList = mutableListOf() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent() + } + + private fun setContent() { + setContent { + NotesDemoAppTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + NotesScreen( + notes = notes, + onLoadNotes = ::loadNotes, + onAddNote = ::addNote, + onDeleteNote = ::deleteNote, + onShareNote = ::share, + ) + } + } + } + } + + private fun loadNotes() = lifecycleScope.launch { + notes.apply { + clear() + addAll(filePersistence.load()) + addAll(databasePersistence.load()) + } + + setContent() + } + + private fun addNote(note: Note) { + lifecycleScope.launch { + when(note) { + is Note.FileNote -> filePersistence.save(note = note) + is Note.DatabaseNote -> databasePersistence.save(note = note) + } + } + notes += note + + setContent() + } + + private fun deleteNote(note: Note) { + lifecycleScope.launch { + when(note) { + is Note.FileNote -> filePersistence.delete(note = note) + is Note.DatabaseNote -> databasePersistence.delete(note = note) + } + } + notes -= note + + setContent() + } + + private fun share(note: Note) { + notesManager.share(note = note) + } +} diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/db/AppDatabase.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/db/AppDatabase.kt similarity index 83% rename from app/src/main/java/com/sjaindl/notesdemoapp/db/AppDatabase.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/db/AppDatabase.kt index d7a83d6..6f86dd4 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/db/AppDatabase.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/db/AppDatabase.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.db +package com.sjaindl.notesdemoapp.model.db import androidx.room.Database import androidx.room.RoomDatabase diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/db/NoteEntity.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/db/NoteEntity.kt similarity index 85% rename from app/src/main/java/com/sjaindl/notesdemoapp/db/NoteEntity.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/db/NoteEntity.kt index c392d1c..e97d30f 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/db/NoteEntity.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/db/NoteEntity.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.db +package com.sjaindl.notesdemoapp.model.db import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/db/NotesDao.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/db/NotesDao.kt similarity index 88% rename from app/src/main/java/com/sjaindl/notesdemoapp/db/NotesDao.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/db/NotesDao.kt index b7d12ed..bb034ec 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/db/NotesDao.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/db/NotesDao.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.db +package com.sjaindl.notesdemoapp.model.db import androidx.room.Dao import androidx.room.Delete diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesDatabasePersistence.kt similarity index 89% rename from app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesDatabasePersistence.kt index 2a2fb32..63cb3d0 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesDatabasePersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesDatabasePersistence.kt @@ -1,7 +1,7 @@ -package com.sjaindl.notesdemoapp.persistence +package com.sjaindl.notesdemoapp.model.persistence -import com.sjaindl.notesdemoapp.db.AppDatabase -import com.sjaindl.notesdemoapp.db.NoteEntity +import com.sjaindl.notesdemoapp.model.db.AppDatabase +import com.sjaindl.notesdemoapp.model.db.NoteEntity import com.sjaindl.notesdemoapp.model.Note import com.sjaindl.notesdemoapp.model.Note.DatabaseNote diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesFilePersistence.kt similarity index 97% rename from app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesFilePersistence.kt index 2997962..68ec6e9 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesFilePersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesFilePersistence.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.persistence +package com.sjaindl.notesdemoapp.model.persistence import android.content.Context import com.sjaindl.notesdemoapp.model.Note diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesPersistence.kt similarity index 64% rename from app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesPersistence.kt index 7235162..f359b5f 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/persistence/NotesPersistence.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/persistence/NotesPersistence.kt @@ -1,6 +1,5 @@ -package com.sjaindl.notesdemoapp.persistence +package com.sjaindl.notesdemoapp.model.persistence -import com.sjaindl.notesdemoapp.NotePersistenceAction import com.sjaindl.notesdemoapp.model.Note interface NotesPersistence { diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/sharing/NoteShareAction.kt similarity index 68% rename from app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/sharing/NoteShareAction.kt index dacba82..8967671 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NoteShareAction.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/sharing/NoteShareAction.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp +package com.sjaindl.notesdemoapp.model.sharing import com.sjaindl.notesdemoapp.model.Note diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt b/app/src/main/java/com/sjaindl/notesdemoapp/model/sharing/NotesManager.kt similarity index 93% rename from app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/model/sharing/NotesManager.kt index 45ab53d..5d39f75 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesManager.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/model/sharing/NotesManager.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp +package com.sjaindl.notesdemoapp.model.sharing import android.content.Context import android.content.Intent diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/AddNoteScreen.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/AddNoteScreen.kt similarity index 98% rename from app/src/main/java/com/sjaindl/notesdemoapp/AddNoteScreen.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/AddNoteScreen.kt index 14c039f..ae91844 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/AddNoteScreen.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/AddNoteScreen.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp +package com.sjaindl.notesdemoapp.view import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.sjaindl.notesdemoapp.R import com.sjaindl.notesdemoapp.model.Note import com.sjaindl.notesdemoapp.model.ShareType import kotlin.random.Random diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesAppBar.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/NotesAppBar.kt similarity index 91% rename from app/src/main/java/com/sjaindl/notesdemoapp/NotesAppBar.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/NotesAppBar.kt index 838b73a..845ce32 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesAppBar.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/NotesAppBar.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp +package com.sjaindl.notesdemoapp.view import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack @@ -13,7 +13,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.sjaindl.notesdemoapp.ui.theme.NotesDemoAppTheme +import com.sjaindl.notesdemoapp.R +import com.sjaindl.notesdemoapp.view.theme.NotesDemoAppTheme @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/NotesScreen.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/NotesScreen.kt similarity index 67% rename from app/src/main/java/com/sjaindl/notesdemoapp/NotesScreen.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/NotesScreen.kt index bef9f28..901899e 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/NotesScreen.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/NotesScreen.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp +package com.sjaindl.notesdemoapp.view import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -13,39 +13,38 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import androidx.lifecycle.viewmodel.compose.viewModel -import com.sjaindl.notesdemoapp.ui.theme.NotesDemoAppTheme +import com.sjaindl.notesdemoapp.model.Note +import com.sjaindl.notesdemoapp.model.ShareType +import com.sjaindl.notesdemoapp.view.theme.NotesDemoAppTheme @Composable fun NotesScreen( + notes: List, modifier: Modifier = Modifier, - viewModel: NotesViewModel = viewModel( - factory = NotesViewModel.NotesViewModelFactory(LocalContext.current) - ) + onLoadNotes: () -> Unit, + onAddNote: (Note) -> Unit, + onDeleteNote: (Note) -> Unit, + onShareNote: (Note) -> Unit, ) { var addNote by remember { mutableStateOf(false) } LaunchedEffect(key1 = Unit) { - viewModel.loadNotes() + onLoadNotes() } - val notes = viewModel.notes.collectAsState() - if (addNote) { AddNoteScreen( modifier = modifier, onAddNote = { - viewModel.addNote(note = it) + onAddNote(it) }, navigateUp = { addNote = false @@ -73,14 +72,14 @@ fun NotesScreen( LazyColumn( modifier = Modifier.padding(padding), ) { - items(notes.value) { note -> + items(notes) { note -> SingleNote( note = note, onDelete = { - viewModel.deleteNote(note = note) + onDeleteNote(note) }, onShare = { - viewModel.share(note = note) + onShareNote(note) }, ) } @@ -94,6 +93,25 @@ fun NotesScreen( @Composable fun NotesScreenPreview() { NotesDemoAppTheme { - NotesScreen() + NotesScreen( + notes = listOf( + Note.FileNote( + id = "id", + shareType = ShareType.Shareable, + title = "file note title", + text = "text", + ), + Note.DatabaseNote( + id = "id", + shareType = ShareType.Unshareable, + title = "database note title", + text = "text", + ), + ), + onLoadNotes = {}, + onAddNote = {}, + onDeleteNote = {}, + onShareNote = {}, + ) } } diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/SingleNote.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/SingleNote.kt similarity index 97% rename from app/src/main/java/com/sjaindl/notesdemoapp/SingleNote.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/SingleNote.kt index 6a5a639..35114f4 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/SingleNote.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/SingleNote.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp +package com.sjaindl.notesdemoapp.view import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.sjaindl.notesdemoapp.model.Note import com.sjaindl.notesdemoapp.model.ShareType -import com.sjaindl.notesdemoapp.ui.theme.NotesDemoAppTheme +import com.sjaindl.notesdemoapp.view.theme.NotesDemoAppTheme @Composable fun SingleNote( diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Color.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Color.kt similarity index 84% rename from app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Color.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Color.kt index c0c104e..36fb59f 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Color.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Color.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.ui.theme +package com.sjaindl.notesdemoapp.view.theme import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Theme.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Theme.kt similarity index 98% rename from app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Theme.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Theme.kt index b2e0b7a..9433826 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Theme.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Theme.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.ui.theme +package com.sjaindl.notesdemoapp.view.theme import android.app.Activity import android.os.Build diff --git a/app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Type.kt b/app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Type.kt similarity index 95% rename from app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Type.kt rename to app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Type.kt index 5bfdb5b..9b9e917 100644 --- a/app/src/main/java/com/sjaindl/notesdemoapp/ui/theme/Type.kt +++ b/app/src/main/java/com/sjaindl/notesdemoapp/view/theme/Type.kt @@ -1,4 +1,4 @@ -package com.sjaindl.notesdemoapp.ui.theme +package com.sjaindl.notesdemoapp.view.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle