ADD: Dialog Composable to add new Collections and ViewModel for NoteBrowser

This commit is contained in:
sebastian 2025-05-10 10:50:46 +02:00
parent d6588913fd
commit 4ff09a6759
9 changed files with 248 additions and 7 deletions

View File

@ -42,6 +42,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import come.stormborntales.notevault.data.local.AppDatabase import come.stormborntales.notevault.data.local.AppDatabase
import come.stormborntales.notevault.data.local.entity.NoteEntity import come.stormborntales.notevault.data.local.entity.NoteEntity
import come.stormborntales.notevault.data.repository.CollectionRepository
import come.stormborntales.notevault.data.repository.NoteRepository import come.stormborntales.notevault.data.repository.NoteRepository
import come.stormborntales.notevault.ui.screens.AddNoteDialog import come.stormborntales.notevault.ui.screens.AddNoteDialog
import come.stormborntales.notevault.ui.screens.MainScreen import come.stormborntales.notevault.ui.screens.MainScreen
@ -59,8 +60,9 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val applicationContext = applicationContext val applicationContext = applicationContext
val database = AppDatabase.getDatabase(applicationContext) val database = AppDatabase.getDatabase(applicationContext)
val repository = NoteRepository(database.noteDao()) val noteRepository = NoteRepository(database.noteDao())
val viewModelFactory = NoteViewModelFactory(repository) val collectionRepository = CollectionRepository(database.collectionDao())
val viewModelFactory = NoteViewModelFactory(noteRepository)
setContent { setContent {
NoteVaultTheme { NoteVaultTheme {
@ -206,7 +208,7 @@ class MainActivity : ComponentActivity() {
SettingsScreen() SettingsScreen()
} }
composable("notes") { composable("notes") {
NotesScreen() NotesScreen(collectionRepository, noteRepository)
} }
} }

View File

@ -6,6 +6,7 @@ import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import come.stormborntales.notevault.data.local.dao.CollectionDao
import come.stormborntales.notevault.data.local.dao.NoteDao import come.stormborntales.notevault.data.local.dao.NoteDao
import come.stormborntales.notevault.data.local.entity.NoteCollection import come.stormborntales.notevault.data.local.entity.NoteCollection
import come.stormborntales.notevault.data.local.entity.NoteEntity import come.stormborntales.notevault.data.local.entity.NoteEntity
@ -14,6 +15,7 @@ import come.stormborntales.notevault.data.local.entity.NoteEntity
@TypeConverters(UriListConverter::class) @TypeConverters(UriListConverter::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao abstract fun noteDao(): NoteDao
abstract fun collectionDao(): CollectionDao
companion object { companion object {
@Volatile @Volatile

View File

@ -0,0 +1,32 @@
package come.stormborntales.notevault.data.local.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import come.stormborntales.notevault.data.local.entity.NoteCollection
import kotlinx.coroutines.flow.Flow
@Dao
interface CollectionDao {
@Query(value = "SELECT * FROM note_collections WHERE parentId IS :parentId")
fun getCollectionsByParent(parentId: Int?): Flow<List<NoteCollection>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCollection(collection: NoteCollection)
@Update
suspend fun updateCollection(collection: NoteCollection)
@Delete
suspend fun deleteCollection(collection: NoteCollection)
@Query("SELECT * FROM note_collections WHERE id = :id LIMIT 1")
suspend fun getById(id: Int): NoteCollection?
@Query("SELECT * FROM note_collections")
fun getAll(): Flow<List<NoteCollection>>
}

View File

@ -22,4 +22,6 @@ interface NoteDao {
@Update @Update
suspend fun update(note: NoteEntity) suspend fun update(note: NoteEntity)
@Query("SELECT * FROM notes WHERE collectionId = :collectionId")
fun getNotesForCollection(collectionId: Int?): Flow<List<NoteEntity>>
} }

View File

@ -0,0 +1,34 @@
package come.stormborntales.notevault.data.repository
import come.stormborntales.notevault.data.local.dao.CollectionDao
import come.stormborntales.notevault.data.local.dao.NoteDao
import come.stormborntales.notevault.data.local.entity.NoteCollection
import come.stormborntales.notevault.data.local.entity.NoteEntity
import kotlinx.coroutines.flow.Flow
class CollectionRepository(private val dao: CollectionDao) {
fun getCollectionsByParent(parentId: Int?): Flow<List<NoteCollection>> {
return dao.getCollectionsByParent(parentId)
}
suspend fun insertCollection(collection: NoteCollection) {
dao.insertCollection(collection)
}
suspend fun updateCollection(collection: NoteCollection) {
dao.updateCollection(collection)
}
suspend fun deleteCollection(collection: NoteCollection) {
dao.deleteCollection(collection)
}
suspend fun getCollectionById(id: Int): NoteCollection? {
return dao.getById(id)
}
fun getAllCollections(): Flow<List<NoteCollection>> {
return dao.getAll()
}
}

View File

@ -11,4 +11,5 @@ class NoteRepository(private val dao: NoteDao) {
suspend fun delete(note: NoteEntity) = dao.delete(note) suspend fun delete(note: NoteEntity) = dao.delete(note)
suspend fun update(note: NoteEntity) = dao.update(note); suspend fun update(note: NoteEntity) = dao.update(note);
fun getNotesForCollection(parentId: Int?) = dao.getNotesForCollection(parentId);
} }

View File

@ -1,27 +1,111 @@
package come.stormborntales.notevault.ui.screens package come.stormborntales.notevault.ui.screens
import android.util.Log
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import come.stormborntales.notevault.data.local.AppDatabase
import come.stormborntales.notevault.data.repository.CollectionRepository
import come.stormborntales.notevault.data.repository.NoteRepository
import come.stormborntales.notevault.ui.viewmodel.NoteBrowserViewModel
import come.stormborntales.notevault.ui.viewmodel.NoteBrowserViewModelFactory
import come.stormborntales.notevault.ui.viewmodel.NoteViewModelFactory
@Composable @Composable
fun NotesScreen() { fun CreateCollectionDialog(
onDismiss: () -> Unit,
onCreate: (String) -> Unit
) {
var collectionName by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text("Neue Collection erstellen")
},
text = {
Column {
OutlinedTextField(
value = collectionName,
onValueChange = {
collectionName = it
isError = false
},
label = { Text("Collection-Name") },
isError = isError,
singleLine = true
)
if (isError) {
Text(
text = "Name darf nicht leer sein",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall
)
}
}
},
confirmButton = {
TextButton(onClick = {
if (collectionName.isBlank()) {
isError = true
} else {
onCreate(collectionName.trim())
onDismiss()
}
}) {
Text("Erstellen")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Abbrechen")
}
}
)
}
@Composable
fun NotesScreen(
collectionRepository: CollectionRepository,
noteRepository: NoteRepository,
) {
var menuExpanded by remember { mutableStateOf(false) } var menuExpanded by remember { mutableStateOf(false) }
var showDialog by remember { mutableStateOf(false) }
val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current)
val viewModel: NoteBrowserViewModel = viewModel(
viewModelStoreOwner,
factory = NoteBrowserViewModelFactory(noteRepository, collectionRepository)
)
val collections by viewModel.currentCollections.collectAsState()
val notes by viewModel.currentNotes.collectAsState()
Scaffold( Scaffold(
floatingActionButton = { floatingActionButton = {
@ -37,14 +121,14 @@ fun NotesScreen() {
.wrapContentWidth() .wrapContentWidth()
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text("Option 1") }, text = { Text("Ordner erstellen") },
onClick = { onClick = {
menuExpanded = false menuExpanded = false
// Handle action showDialog = true
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("Option 2") }, text = { Text("Noten importieren") },
onClick = { onClick = {
menuExpanded = false menuExpanded = false
// Handle action // Handle action
@ -60,6 +144,16 @@ fun NotesScreen() {
.padding(innerPadding)) { .padding(innerPadding)) {
Text("Inhalt deiner App", modifier = Modifier.align(Alignment.Center)) Text("Inhalt deiner App", modifier = Modifier.align(Alignment.Center))
} }
if (showDialog) {
CreateCollectionDialog(
onDismiss = { showDialog = false },
onCreate = { name ->
// Handle the new collection name
Log.d("NotesBrowser", "onCreate: $name")
}
)
}
} }
) )
} }

View File

@ -0,0 +1,54 @@
package come.stormborntales.notevault.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import come.stormborntales.notevault.data.local.entity.NoteCollection
import come.stormborntales.notevault.data.local.entity.NoteEntity
import come.stormborntales.notevault.data.repository.CollectionRepository
import come.stormborntales.notevault.data.repository.NoteRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class NoteBrowserViewModel (
private val noteRepository: NoteRepository,
private val collectionRepository: CollectionRepository
) : ViewModel() {
private val _currentCollections = MutableStateFlow<List<NoteCollection>>(emptyList())
val currentCollections: StateFlow<List<NoteCollection>> = _currentCollections.asStateFlow()
private val _currentNotes = MutableStateFlow<List<NoteEntity>>(emptyList())
val currentNotes: StateFlow<List<NoteEntity>> = _currentNotes.asStateFlow()
private val _currentParentId = MutableStateFlow<Int?>(null)
val currentParentId: StateFlow<Int?> = _currentParentId.asStateFlow()
init {
loadContentForParent(null)
}
fun loadContentForParent(parentId: Int?) {
_currentParentId.value = parentId
viewModelScope.launch {
launch {
collectionRepository.getCollectionsByParent(parentId).collect {
_currentCollections.value = it
}
}
launch {
noteRepository.getNotesForCollection(parentId).collect {
_currentNotes.value = it
}
}
}
}
fun createCollection(name: String) {
viewModelScope.launch {
val newCollection = NoteCollection(name = name, parentId = _currentParentId.value)
collectionRepository.insertCollection(newCollection)
loadContentForParent(_currentParentId.value)
}
}
}

View File

@ -0,0 +1,20 @@
package come.stormborntales.notevault.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import come.stormborntales.notevault.data.repository.CollectionRepository
import come.stormborntales.notevault.data.repository.NoteRepository
@Suppress("UNCHECKED_CAST")
class NoteBrowserViewModelFactory (
private val noteRepository: NoteRepository,
private val collectionRepository: CollectionRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(NoteBrowserViewModel::class.java)) {
return NoteBrowserViewModel(noteRepository, collectionRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}