Compare commits
	
		
			15 Commits
		
	
	
		
			master
			...
			nextNoteVa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8e8eecf367 | ||
| 
						 | 
					71d56519b2 | ||
| 
						 | 
					2525ef9eac | ||
| 
						 | 
					0c1bc5265e | ||
| 
						 | 
					8989dcd21d | ||
| 
						 | 
					6df3f756ec | ||
| 
						 | 
					caece7bfea | ||
| 
						 | 
					234a5a7a60 | ||
| 
						 | 
					dd7e9dce65 | ||
| 
						 | 
					b9fe9cf184 | ||
| 
						 | 
					828a4e0fd3 | ||
| 
						 | 
					a61493c272 | ||
| 
						 | 
					4ff09a6759 | ||
| 
						 | 
					d6588913fd | ||
| 
						 | 
					4a44f99b86 | 
@ -1,49 +1,7 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
 | 
			
		||||
    <data-source source="LOCAL" name="music_database" uuid="95a3c2ec-2c29-4336-900a-3993de90ae66">
 | 
			
		||||
      <driver-ref>sqlite.xerial</driver-ref>
 | 
			
		||||
      <synchronize>true</synchronize>
 | 
			
		||||
      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
			
		||||
      <jdbc-url>jdbc:sqlite:$USER_HOME$/.cache/JetBrains/IntelliJIdea2025.1/device-explorer/samsung SM-P610/_/data/data/com.stormtales.notevault/databases/music_database</jdbc-url>
 | 
			
		||||
      <jdbc-additional-properties>
 | 
			
		||||
        <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
 | 
			
		||||
      </jdbc-additional-properties>
 | 
			
		||||
      <working-dir>$ProjectFileDir$</working-dir>
 | 
			
		||||
      <libraries>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
      </libraries>
 | 
			
		||||
    </data-source>
 | 
			
		||||
    <data-source source="LOCAL" name="note_database" uuid="b3770d7c-0a73-40c6-aab8-010effaa19b6">
 | 
			
		||||
      <driver-ref>sqlite.xerial</driver-ref>
 | 
			
		||||
      <synchronize>true</synchronize>
 | 
			
		||||
      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
			
		||||
      <jdbc-url>jdbc:sqlite:$USER_HOME$/.cache/JetBrains/IntelliJIdea2025.1/device-explorer/samsung SM-P610/_/data/data/come.stormborntales.notevault/databases/note_database</jdbc-url>
 | 
			
		||||
      <jdbc-additional-properties>
 | 
			
		||||
        <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
 | 
			
		||||
      </jdbc-additional-properties>
 | 
			
		||||
      <working-dir>$ProjectFileDir$</working-dir>
 | 
			
		||||
      <libraries>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
      </libraries>
 | 
			
		||||
    </data-source>
 | 
			
		||||
    <data-source source="LOCAL" name="note_database [2]" uuid="ad94cfd9-e485-4151-8bf4-a080c51fa27c">
 | 
			
		||||
    <data-source source="LOCAL" name="note_database" uuid="669634a7-d50e-4c04-b2fe-786d18bbd166">
 | 
			
		||||
      <driver-ref>sqlite.xerial</driver-ref>
 | 
			
		||||
      <synchronize>true</synchronize>
 | 
			
		||||
      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
 | 
			
		||||
@ -65,6 +23,12 @@
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
      </libraries>
 | 
			
		||||
    </data-source>
 | 
			
		||||
  </component>
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@
 | 
			
		||||
    <selectionStates>
 | 
			
		||||
      <SelectionState runConfigName="app">
 | 
			
		||||
        <option name="selectionMode" value="DROPDOWN" />
 | 
			
		||||
        <DropdownSelection timestamp="2025-05-03T08:34:29.354537334Z">
 | 
			
		||||
        <DropdownSelection timestamp="2025-05-10T10:58:30.749991183Z">
 | 
			
		||||
          <Target type="DEFAULT_BOOT">
 | 
			
		||||
            <handle>
 | 
			
		||||
              <DeviceId pluginId="PhysicalDevice" identifier="serial=R52N50NLGRT" />
 | 
			
		||||
 | 
			
		||||
@ -63,4 +63,5 @@ dependencies {
 | 
			
		||||
    implementation(libs.compose.runtime.livedata)
 | 
			
		||||
    implementation(libs.coil.compose)
 | 
			
		||||
    ksp(libs.androidx.room.compiler)
 | 
			
		||||
    implementation(libs.androidx.material.icons.extended)
 | 
			
		||||
}
 | 
			
		||||
@ -42,12 +42,15 @@ import androidx.navigation.compose.currentBackStackEntryAsState
 | 
			
		||||
import androidx.navigation.compose.rememberNavController
 | 
			
		||||
import come.stormborntales.notevault.data.local.AppDatabase
 | 
			
		||||
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.ui.screens.AddNoteDialog
 | 
			
		||||
import come.stormborntales.notevault.ui.screens.MainScreen
 | 
			
		||||
import come.stormborntales.notevault.ui.screens.NotesScreen
 | 
			
		||||
import come.stormborntales.notevault.ui.screens.notebrowser.NotesScreen
 | 
			
		||||
import come.stormborntales.notevault.ui.screens.SettingsScreen
 | 
			
		||||
import come.stormborntales.notevault.ui.theme.NoteVaultTheme
 | 
			
		||||
import come.stormborntales.notevault.ui.viewmodel.NoteBrowserViewModel
 | 
			
		||||
import come.stormborntales.notevault.ui.viewmodel.NoteBrowserViewModelFactory
 | 
			
		||||
import come.stormborntales.notevault.ui.viewmodel.NoteViewModel
 | 
			
		||||
import come.stormborntales.notevault.ui.viewmodel.NoteViewModelFactory
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
@ -59,14 +62,16 @@ class MainActivity : ComponentActivity() {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        val applicationContext = applicationContext
 | 
			
		||||
        val database = AppDatabase.getDatabase(applicationContext)
 | 
			
		||||
        val repository = NoteRepository(database.noteDao())
 | 
			
		||||
        val viewModelFactory = NoteViewModelFactory(repository)
 | 
			
		||||
        val noteRepository = NoteRepository(database.noteDao())
 | 
			
		||||
        val collectionRepository = CollectionRepository(database.collectionDao())
 | 
			
		||||
        val viewModelFactory = NoteViewModelFactory(noteRepository)
 | 
			
		||||
        val noteBrowserViewModelFactory = NoteBrowserViewModelFactory(noteRepository, collectionRepository)
 | 
			
		||||
 | 
			
		||||
        setContent {
 | 
			
		||||
            NoteVaultTheme {
 | 
			
		||||
                val navController = rememberNavController()
 | 
			
		||||
                val viewModel: NoteViewModel = viewModel(factory = viewModelFactory)
 | 
			
		||||
 | 
			
		||||
                val noteBrowserViewModel: NoteBrowserViewModel = viewModel(factory = noteBrowserViewModelFactory)
 | 
			
		||||
                var selectedUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
 | 
			
		||||
                var showDialog by remember { mutableStateOf(false) }
 | 
			
		||||
                var noteToEdit by remember { mutableStateOf<NoteEntity?>(null) }
 | 
			
		||||
@ -206,7 +211,13 @@ class MainActivity : ComponentActivity() {
 | 
			
		||||
                                SettingsScreen()
 | 
			
		||||
                            }
 | 
			
		||||
                            composable("notes") {
 | 
			
		||||
                                NotesScreen()
 | 
			
		||||
                                NotesScreen(collectionRepository, noteRepository, onImportNotes = {
 | 
			
		||||
                                    imagePickerLauncher.launch(arrayOf("image/*"))
 | 
			
		||||
                                }, noteBrowserViewModel,
 | 
			
		||||
                                    onEditNote = {
 | 
			
		||||
                                        noteToEdit = it
 | 
			
		||||
                                        showDialog = true
 | 
			
		||||
                                    })
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -224,6 +235,7 @@ class MainActivity : ComponentActivity() {
 | 
			
		||||
                                            genre = genre,
 | 
			
		||||
                                            description = description,
 | 
			
		||||
                                            selectedUris = selectedUris,
 | 
			
		||||
                                            collectionID = noteBrowserViewModel.currentParentId.value,
 | 
			
		||||
                                            onDone = { showDialog = false }
 | 
			
		||||
                                        )
 | 
			
		||||
                                    } else {
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import androidx.room.Database
 | 
			
		||||
import androidx.room.Room
 | 
			
		||||
import androidx.room.RoomDatabase
 | 
			
		||||
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.entity.NoteCollection
 | 
			
		||||
import come.stormborntales.notevault.data.local.entity.NoteEntity
 | 
			
		||||
@ -14,6 +15,7 @@ import come.stormborntales.notevault.data.local.entity.NoteEntity
 | 
			
		||||
@TypeConverters(UriListConverter::class)
 | 
			
		||||
abstract class AppDatabase : RoomDatabase() {
 | 
			
		||||
    abstract fun noteDao(): NoteDao
 | 
			
		||||
    abstract fun collectionDao(): CollectionDao
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        @Volatile
 | 
			
		||||
 | 
			
		||||
@ -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>>
 | 
			
		||||
}
 | 
			
		||||
@ -22,4 +22,6 @@ interface NoteDao {
 | 
			
		||||
 | 
			
		||||
    @Update
 | 
			
		||||
    suspend fun update(note: NoteEntity)
 | 
			
		||||
    @Query("SELECT * FROM notes WHERE collectionId IS :collectionId")
 | 
			
		||||
    fun getNotesForCollection(collectionId: Int?): Flow<List<NoteEntity>>
 | 
			
		||||
}
 | 
			
		||||
@ -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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -11,4 +11,5 @@ class NoteRepository(private val dao: NoteDao) {
 | 
			
		||||
    suspend fun delete(note: NoteEntity) = dao.delete(note)
 | 
			
		||||
 | 
			
		||||
    suspend fun update(note: NoteEntity) = dao.update(note);
 | 
			
		||||
    fun getNotesForCollection(parentId: Int?) = dao.getNotesForCollection(parentId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,8 @@ fun AddNoteDialog(
 | 
			
		||||
    initialComposer: String? = null,
 | 
			
		||||
    initialYear: Int? = null,
 | 
			
		||||
    initialGenre: String? = null,
 | 
			
		||||
    initialDescription: String? = null
 | 
			
		||||
    initialDescription: String? = null,
 | 
			
		||||
    collectionId: Int? = null
 | 
			
		||||
) {
 | 
			
		||||
    var title by remember { mutableStateOf(initialTitle) }
 | 
			
		||||
    var composer by remember { mutableStateOf(initialComposer ?: "") }
 | 
			
		||||
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.screens
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.material3.Divider
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.OutlinedButton
 | 
			
		||||
import androidx.compose.material3.Switch
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun NotesScreen() {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,82 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.screens.notebrowser
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.ButtonDefaults
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Slider
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.TextButton
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
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.unit.dp
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun DeleteCollectionDialog(
 | 
			
		||||
    collectionName: String,
 | 
			
		||||
    onDismiss: () -> Unit,
 | 
			
		||||
    onConfirm: () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    var sliderPosition by remember { mutableStateOf(0f) }
 | 
			
		||||
 | 
			
		||||
    AlertDialog(
 | 
			
		||||
        onDismissRequest = onDismiss,
 | 
			
		||||
        title = {
 | 
			
		||||
            Text("Collection löschen", style = MaterialTheme.typography.titleLarge)
 | 
			
		||||
        },
 | 
			
		||||
        text = {
 | 
			
		||||
            Column {
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = "Bist du sicher, dass du die Collection \"$collectionName\" löschen möchtest?",
 | 
			
		||||
                    style = MaterialTheme.typography.bodyMedium
 | 
			
		||||
                )
 | 
			
		||||
                Spacer(modifier = Modifier.height(8.dp))
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = "⚠️ Alle enthaltenen Noten werden ebenfalls dauerhaft gelöscht.",
 | 
			
		||||
                    style = MaterialTheme.typography.bodySmall,
 | 
			
		||||
                    color = MaterialTheme.colorScheme.error
 | 
			
		||||
                )
 | 
			
		||||
                Spacer(modifier = Modifier.height(16.dp))
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = "Zum Bestätigen den Schieberegler ganz nach rechts ziehen.",
 | 
			
		||||
                    style = MaterialTheme.typography.bodySmall
 | 
			
		||||
                )
 | 
			
		||||
                Spacer(modifier = Modifier.height(8.dp))
 | 
			
		||||
                Slider(
 | 
			
		||||
                    value = sliderPosition,
 | 
			
		||||
                    onValueChange = { sliderPosition = it },
 | 
			
		||||
                    valueRange = 0f..1f,
 | 
			
		||||
                    steps = 0,
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .fillMaxWidth()
 | 
			
		||||
                        .padding(horizontal = 4.dp)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        confirmButton = {
 | 
			
		||||
            TextButton(onClick = {
 | 
			
		||||
                if(sliderPosition == 1.0f) {
 | 
			
		||||
                    onConfirm()
 | 
			
		||||
                }
 | 
			
		||||
            },  colors = ButtonDefaults.textButtonColors(
 | 
			
		||||
                contentColor = MaterialTheme.colorScheme.error
 | 
			
		||||
            )) {
 | 
			
		||||
                Text("Löschen")
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dismissButton = {
 | 
			
		||||
            TextButton(onClick = onDismiss) {
 | 
			
		||||
                Text("Abbrechen")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,150 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.screens.notebrowser
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.layout.width
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.filled.ArrowDropDown
 | 
			
		||||
import androidx.compose.material.icons.filled.Delete
 | 
			
		||||
import androidx.compose.material.icons.filled.Edit
 | 
			
		||||
import androidx.compose.material.icons.filled.Folder
 | 
			
		||||
import androidx.compose.material.ripple.rememberRipple
 | 
			
		||||
import androidx.compose.material3.DropdownMenu
 | 
			
		||||
import androidx.compose.material3.DropdownMenuItem
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.text.style.TextAlign
 | 
			
		||||
import androidx.compose.ui.text.style.TextOverflow
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import coil.compose.AsyncImage
 | 
			
		||||
import come.stormborntales.notevault.data.local.entity.NoteCollection
 | 
			
		||||
import come.stormborntales.notevault.data.local.entity.NoteEntity
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun CollectionItem(
 | 
			
		||||
    collection: NoteCollection,
 | 
			
		||||
    onDeleteCollection: (NoteCollection) -> Unit,
 | 
			
		||||
    onEditCollection: (NoteCollection) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    var showMenu by remember { mutableStateOf(false) }
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .height(120.dp)
 | 
			
		||||
            .clip(RoundedCornerShape(12.dp))
 | 
			
		||||
            .background(MaterialTheme.colorScheme.surfaceVariant)
 | 
			
		||||
            .clickable { }
 | 
			
		||||
            .padding(12.dp),
 | 
			
		||||
        contentAlignment = Alignment.Center
 | 
			
		||||
    ) {
 | 
			
		||||
        Column(
 | 
			
		||||
            horizontalAlignment = Alignment.CenterHorizontally
 | 
			
		||||
        ) {
 | 
			
		||||
            Icon(
 | 
			
		||||
                imageVector = Icons.Filled.Folder,
 | 
			
		||||
                contentDescription = "Ordner",
 | 
			
		||||
                tint = MaterialTheme.colorScheme.primary,
 | 
			
		||||
                modifier = Modifier.size(64.dp)
 | 
			
		||||
            )
 | 
			
		||||
            Box {
 | 
			
		||||
                val interactionSource = remember { MutableInteractionSource() }
 | 
			
		||||
                Row(
 | 
			
		||||
                    verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .clickable { showMenu = true }
 | 
			
		||||
                        .padding(top = 8.dp)
 | 
			
		||||
                ) {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        text = collection.name,
 | 
			
		||||
                        style = MaterialTheme.typography.bodyMedium,
 | 
			
		||||
                        textAlign = TextAlign.Center,
 | 
			
		||||
                        maxLines = 2,
 | 
			
		||||
                        overflow = TextOverflow.Ellipsis,
 | 
			
		||||
                        modifier = Modifier
 | 
			
		||||
                            .clickable(
 | 
			
		||||
                                interactionSource = interactionSource,
 | 
			
		||||
                                indication = rememberRipple(bounded = true)
 | 
			
		||||
                            ) {
 | 
			
		||||
                                showMenu = true
 | 
			
		||||
                            }
 | 
			
		||||
                    )
 | 
			
		||||
                    Icon(
 | 
			
		||||
                        imageVector = Icons.Default.ArrowDropDown,
 | 
			
		||||
                        contentDescription = "Mehr Optionen",
 | 
			
		||||
                        tint = MaterialTheme.colorScheme.onSurface
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                DropdownMenu(
 | 
			
		||||
                    expanded = showMenu,
 | 
			
		||||
                    onDismissRequest = { showMenu = false },
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .width(240.dp)
 | 
			
		||||
                        .background(MaterialTheme.colorScheme.background)
 | 
			
		||||
                ) {
 | 
			
		||||
                    DropdownMenuItem(
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            showMenu = false
 | 
			
		||||
                            onEditCollection(collection)
 | 
			
		||||
                        },
 | 
			
		||||
                        text = {
 | 
			
		||||
                            Row(
 | 
			
		||||
                                verticalAlignment = Alignment.CenterVertically
 | 
			
		||||
                            ) {
 | 
			
		||||
                                Icon(
 | 
			
		||||
                                    imageVector = Icons.Default.Edit,
 | 
			
		||||
                                    contentDescription = "Bearbeiten",
 | 
			
		||||
                                    tint = MaterialTheme.colorScheme.primary,
 | 
			
		||||
                                    modifier = Modifier.size(20.dp)
 | 
			
		||||
                                )
 | 
			
		||||
                                Spacer(modifier = Modifier.width(8.dp))
 | 
			
		||||
                                Text("Bearbeiten", color = MaterialTheme.colorScheme.primary)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                    DropdownMenuItem(
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            showMenu = false
 | 
			
		||||
                            onDeleteCollection(collection)
 | 
			
		||||
                        },
 | 
			
		||||
                        text = {
 | 
			
		||||
                            Row(
 | 
			
		||||
                                verticalAlignment = Alignment.CenterVertically
 | 
			
		||||
                            ) {
 | 
			
		||||
                                Icon(
 | 
			
		||||
                                    imageVector = Icons.Default.Delete,
 | 
			
		||||
                                    contentDescription = "Löschen",
 | 
			
		||||
                                    tint = MaterialTheme.colorScheme.error,
 | 
			
		||||
                                    modifier = Modifier.size(20.dp)
 | 
			
		||||
                                )
 | 
			
		||||
                                Spacer(modifier = Modifier.width(8.dp))
 | 
			
		||||
                                Text("Löschen", color = MaterialTheme.colorScheme.error)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,150 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.screens.notebrowser
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.layout.width
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.filled.ArrowDropDown
 | 
			
		||||
import androidx.compose.material.icons.filled.Delete
 | 
			
		||||
import androidx.compose.material.icons.filled.Edit
 | 
			
		||||
import androidx.compose.material.ripple.rememberRipple
 | 
			
		||||
import androidx.compose.material3.DropdownMenu
 | 
			
		||||
import androidx.compose.material3.DropdownMenuItem
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.text.style.TextAlign
 | 
			
		||||
import androidx.compose.ui.text.style.TextOverflow
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import coil.compose.AsyncImage
 | 
			
		||||
import come.stormborntales.notevault.data.local.entity.NoteEntity
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun NoteItem(
 | 
			
		||||
    note: NoteEntity,
 | 
			
		||||
    onNoteClick: (NoteEntity) -> Unit,
 | 
			
		||||
    onEditTitle: () -> Unit,
 | 
			
		||||
    onDeleteNote: () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    var showMenu by remember { mutableStateOf(false) }
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .height(120.dp)
 | 
			
		||||
            .clip(RoundedCornerShape(12.dp))
 | 
			
		||||
            .background(MaterialTheme.colorScheme.surfaceVariant)
 | 
			
		||||
            .clickable { onNoteClick(note) }
 | 
			
		||||
            .padding(12.dp),
 | 
			
		||||
        contentAlignment = Alignment.Center
 | 
			
		||||
    ) {
 | 
			
		||||
        Column(
 | 
			
		||||
            horizontalAlignment = Alignment.CenterHorizontally
 | 
			
		||||
        ) {
 | 
			
		||||
            AsyncImage(
 | 
			
		||||
                model = note.imagePreview,
 | 
			
		||||
                contentDescription = "Noten-Vorschau",
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .size(64.dp)
 | 
			
		||||
                    .clip(RoundedCornerShape(8.dp)),
 | 
			
		||||
                contentScale = ContentScale.Crop
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            Box {
 | 
			
		||||
                val interactionSource = remember { MutableInteractionSource() }
 | 
			
		||||
                Row(
 | 
			
		||||
                    verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .clickable { showMenu = true }
 | 
			
		||||
                        .padding(top = 8.dp)
 | 
			
		||||
                ) {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        text = note.title,
 | 
			
		||||
                        style = MaterialTheme.typography.bodyMedium,
 | 
			
		||||
                        textAlign = TextAlign.Center,
 | 
			
		||||
                        maxLines = 2,
 | 
			
		||||
                        overflow = TextOverflow.Ellipsis,
 | 
			
		||||
                        modifier = Modifier
 | 
			
		||||
                            .clickable(
 | 
			
		||||
                                interactionSource = interactionSource,
 | 
			
		||||
                                indication = rememberRipple(bounded = true)
 | 
			
		||||
                            ) {
 | 
			
		||||
                                showMenu = true
 | 
			
		||||
                            }
 | 
			
		||||
                    )
 | 
			
		||||
                    Icon(
 | 
			
		||||
                        imageVector = Icons.Default.ArrowDropDown,
 | 
			
		||||
                        contentDescription = "Mehr Optionen",
 | 
			
		||||
                        tint = MaterialTheme.colorScheme.onSurface
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                DropdownMenu(
 | 
			
		||||
                    expanded = showMenu,
 | 
			
		||||
                    onDismissRequest = { showMenu = false },
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .width(240.dp)
 | 
			
		||||
                        .background(MaterialTheme.colorScheme.background)
 | 
			
		||||
                ) {
 | 
			
		||||
                    DropdownMenuItem(
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            showMenu = false
 | 
			
		||||
                            onEditTitle()
 | 
			
		||||
                        },
 | 
			
		||||
                        text = {
 | 
			
		||||
                            Row(
 | 
			
		||||
                                verticalAlignment = Alignment.CenterVertically
 | 
			
		||||
                            ) {
 | 
			
		||||
                                Icon(
 | 
			
		||||
                                    imageVector = Icons.Default.Edit,
 | 
			
		||||
                                    contentDescription = "Bearbeiten",
 | 
			
		||||
                                    tint = MaterialTheme.colorScheme.primary,
 | 
			
		||||
                                    modifier = Modifier.size(20.dp)
 | 
			
		||||
                                )
 | 
			
		||||
                                Spacer(modifier = Modifier.width(8.dp))
 | 
			
		||||
                                Text("Bearbeiten", color = MaterialTheme.colorScheme.primary)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                    DropdownMenuItem(
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            showMenu = false
 | 
			
		||||
                            onDeleteNote()
 | 
			
		||||
                        },
 | 
			
		||||
                        text = {
 | 
			
		||||
                            Row(
 | 
			
		||||
                                verticalAlignment = Alignment.CenterVertically
 | 
			
		||||
                            ) {
 | 
			
		||||
                                Icon(
 | 
			
		||||
                                    imageVector = Icons.Default.Delete,
 | 
			
		||||
                                    contentDescription = "Löschen",
 | 
			
		||||
                                    tint = MaterialTheme.colorScheme.error,
 | 
			
		||||
                                    modifier = Modifier.size(20.dp)
 | 
			
		||||
                                )
 | 
			
		||||
                                Spacer(modifier = Modifier.width(8.dp))
 | 
			
		||||
                                Text("Löschen", color = MaterialTheme.colorScheme.error)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,305 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.screens.notebrowser
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.activity.compose.BackHandler
 | 
			
		||||
import androidx.compose.foundation.ExperimentalFoundationApi
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.layout.width
 | 
			
		||||
import androidx.compose.foundation.layout.wrapContentWidth
 | 
			
		||||
import androidx.compose.foundation.lazy.grid.GridCells
 | 
			
		||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 | 
			
		||||
import androidx.compose.foundation.lazy.grid.items
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.filled.Add
 | 
			
		||||
import androidx.compose.material.icons.filled.ArrowDropDown
 | 
			
		||||
import androidx.compose.material.icons.filled.Delete
 | 
			
		||||
import androidx.compose.material.icons.filled.Edit
 | 
			
		||||
import androidx.compose.material.icons.filled.Folder
 | 
			
		||||
import androidx.compose.material.ripple.rememberRipple
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.DropdownMenu
 | 
			
		||||
import androidx.compose.material3.DropdownMenuItem
 | 
			
		||||
import androidx.compose.material3.FloatingActionButton
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.OutlinedTextField
 | 
			
		||||
import androidx.compose.material3.Scaffold
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.TextButton
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
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.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
import androidx.compose.ui.layout.ContentScale
 | 
			
		||||
import androidx.compose.ui.text.style.TextAlign
 | 
			
		||||
import androidx.compose.ui.text.style.TextOverflow
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import coil.compose.AsyncImage
 | 
			
		||||
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 come.stormborntales.notevault.ui.viewmodel.NoteBrowserViewModel
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun CreateCollectionDialog(
 | 
			
		||||
    editedCollection: NoteCollection?,
 | 
			
		||||
    onDismiss: () -> Unit,
 | 
			
		||||
    onCreate: (String) -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    var collectionName by remember { mutableStateOf(editedCollection?.name ?: "") }
 | 
			
		||||
    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 CollectionBreadcrumbs(
 | 
			
		||||
    path: List<NoteCollection>,
 | 
			
		||||
    onNavigateTo: (index: Int) -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    Row(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxWidth()
 | 
			
		||||
            .padding(8.dp),
 | 
			
		||||
        verticalAlignment = Alignment.CenterVertically
 | 
			
		||||
    ) {
 | 
			
		||||
        BreadcrumbItem(label = "Start", onClick = { onNavigateTo(-1) })
 | 
			
		||||
 | 
			
		||||
        path.forEachIndexed { index, collection ->
 | 
			
		||||
            Text(" / ", style = MaterialTheme.typography.labelLarge)
 | 
			
		||||
 | 
			
		||||
            BreadcrumbItem(label = collection.name, onClick = {
 | 
			
		||||
                Log.d("Breadcrumb", "BreadcrumbItem " + collection.name + " was clicked")
 | 
			
		||||
                onNavigateTo(index)
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun BreadcrumbItem(label: String, onClick: () -> Unit) {
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .padding(horizontal = 4.dp)
 | 
			
		||||
            .clickable(onClick = {
 | 
			
		||||
                Log.d("Individual Breadcrumb", "A simple test " + label)
 | 
			
		||||
                onClick()
 | 
			
		||||
            })
 | 
			
		||||
    ) {
 | 
			
		||||
        Text(
 | 
			
		||||
            text = label,
 | 
			
		||||
            style = MaterialTheme.typography.labelLarge,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalFoundationApi::class)
 | 
			
		||||
@Composable
 | 
			
		||||
fun NoteGrid(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    collections: List<NoteCollection>,
 | 
			
		||||
    notes: List<NoteEntity>,
 | 
			
		||||
    onCollectionClick: (NoteCollection) -> Unit,
 | 
			
		||||
    onNoteClick: (NoteEntity) -> Unit,
 | 
			
		||||
    onDeleteNote: (NoteEntity) -> Unit,
 | 
			
		||||
    onEditNote: (NoteEntity) ->Unit,
 | 
			
		||||
    onDeleteCollection: (NoteCollection) -> Unit,
 | 
			
		||||
    onEditCollection: (NoteCollection) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    var showCollectionConfirmationDialog by remember { mutableStateOf(false) }
 | 
			
		||||
    var deletedCollection: NoteCollection? by remember { mutableStateOf(null) }
 | 
			
		||||
    LazyVerticalGrid(
 | 
			
		||||
        modifier = modifier.fillMaxSize(),
 | 
			
		||||
        columns = GridCells.Adaptive(minSize = 240.dp),
 | 
			
		||||
        contentPadding = PaddingValues(8.dp),
 | 
			
		||||
        verticalArrangement = Arrangement.spacedBy(12.dp),
 | 
			
		||||
        horizontalArrangement = Arrangement.spacedBy(12.dp)
 | 
			
		||||
    ) {
 | 
			
		||||
        items(collections) { collection ->
 | 
			
		||||
            CollectionItem(collection, onDeleteCollection = {
 | 
			
		||||
                showCollectionConfirmationDialog = true
 | 
			
		||||
                deletedCollection = collection
 | 
			
		||||
            }, onEditCollection = onEditCollection)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        items(notes) { note ->
 | 
			
		||||
            NoteItem(note, onNoteClick = {}, onEditTitle = {onEditNote(note)}, onDeleteNote = {onDeleteNote(note)})
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if(showCollectionConfirmationDialog && deletedCollection != null) {
 | 
			
		||||
        DeleteCollectionDialog(deletedCollection!!.name , {}, {
 | 
			
		||||
            onDeleteCollection(deletedCollection!!)
 | 
			
		||||
            showCollectionConfirmationDialog = false
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@Composable
 | 
			
		||||
fun NotesScreen(
 | 
			
		||||
    collectionRepository: CollectionRepository,
 | 
			
		||||
    noteRepository: NoteRepository,
 | 
			
		||||
    onImportNotes: (Int?) -> Unit,
 | 
			
		||||
    viewModel: NoteBrowserViewModel,
 | 
			
		||||
    onEditNote: (NoteEntity) -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    var menuExpanded by remember { mutableStateOf(false) }
 | 
			
		||||
    var showDialog by remember { mutableStateOf(false) }
 | 
			
		||||
    var collectionToEdit by remember { mutableStateOf<NoteCollection?>(null) }
 | 
			
		||||
 | 
			
		||||
    val collections by viewModel.currentCollections.collectAsState()
 | 
			
		||||
    val notes by viewModel.currentNotes.collectAsState()
 | 
			
		||||
 | 
			
		||||
    Scaffold(
 | 
			
		||||
        floatingActionButton = {
 | 
			
		||||
            Box {
 | 
			
		||||
                FloatingActionButton(onClick = { menuExpanded = true }) {
 | 
			
		||||
                    Icon(Icons.Default.Add, contentDescription = "Open menu")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                DropdownMenu(
 | 
			
		||||
                    expanded = menuExpanded,
 | 
			
		||||
                    onDismissRequest = { menuExpanded = false },
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .wrapContentWidth()
 | 
			
		||||
                ) {
 | 
			
		||||
                    DropdownMenuItem(
 | 
			
		||||
                        text = { Text("Ordner erstellen") },
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            menuExpanded = false
 | 
			
		||||
                            showDialog = true
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                    DropdownMenuItem(
 | 
			
		||||
                        text = { Text("Noten importieren") },
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            menuExpanded = false
 | 
			
		||||
                            onImportNotes(viewModel.currentParentId.value)
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        content = { innerPadding ->
 | 
			
		||||
 | 
			
		||||
            val path by viewModel.pathStack.collectAsState()
 | 
			
		||||
 | 
			
		||||
            BackHandler(enabled = path.isNotEmpty()) {
 | 
			
		||||
                viewModel.navigateUp()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Column(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .fillMaxSize()
 | 
			
		||||
                    .padding(innerPadding)
 | 
			
		||||
            ) {
 | 
			
		||||
                // Breadcrumbs sichtbar & klickbar machen
 | 
			
		||||
                CollectionBreadcrumbs(
 | 
			
		||||
                    path = path,
 | 
			
		||||
                    onNavigateTo = { index ->
 | 
			
		||||
                        Log.d("NoteBrowser", "Navigate to: $index")
 | 
			
		||||
                        if (index == -1) viewModel.loadContentForParent(null)
 | 
			
		||||
                        else viewModel.navigateToLevel(index)
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                NoteGrid(
 | 
			
		||||
                    collections = collections,
 | 
			
		||||
                    notes = notes,
 | 
			
		||||
                    onCollectionClick = { viewModel.loadContentForParent(it.id, it) },
 | 
			
		||||
                    onNoteClick = { /* TODO: Öffnen oder Bearbeiten */ },
 | 
			
		||||
                    onDeleteNote = {
 | 
			
		||||
                        viewModel.removeNote(noteEntity = it)
 | 
			
		||||
                    },
 | 
			
		||||
                    onEditNote = onEditNote,
 | 
			
		||||
                    onDeleteCollection = {
 | 
			
		||||
                        viewModel.removeCollection(noteCollection = it)
 | 
			
		||||
                    },
 | 
			
		||||
                    onEditCollection = {
 | 
			
		||||
                        collectionToEdit = it
 | 
			
		||||
                        showDialog = true
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            if (showDialog) {
 | 
			
		||||
                CreateCollectionDialog(
 | 
			
		||||
                    onDismiss = { showDialog = false },
 | 
			
		||||
                    onCreate = { name ->
 | 
			
		||||
                       if(collectionToEdit != null) {
 | 
			
		||||
                           viewModel.updateCollection(collectionToEdit!!, name)
 | 
			
		||||
                       } else {
 | 
			
		||||
                           // Handle the new collection name
 | 
			
		||||
                           viewModel.createCollection(name)
 | 
			
		||||
                       }
 | 
			
		||||
                    },
 | 
			
		||||
                    editedCollection = collectionToEdit
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,103 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.viewmodel
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
			
		||||
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()
 | 
			
		||||
 | 
			
		||||
    private val _pathStack = MutableStateFlow<List<NoteCollection>>(emptyList())
 | 
			
		||||
    val pathStack: StateFlow<List<NoteCollection>> = _pathStack.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        loadContentForParent(null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadContentForParent(parentId: Int?, selectedCollection: NoteCollection? = null) {
 | 
			
		||||
        _currentParentId.value = parentId
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            launch {
 | 
			
		||||
                collectionRepository.getCollectionsByParent(parentId).collect {
 | 
			
		||||
                    _currentCollections.value = it
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            launch {
 | 
			
		||||
                noteRepository.getNotesForCollection(parentId).collect {
 | 
			
		||||
                    Log.d("NoteBrowser", "Noten geladen: ${it.size}")
 | 
			
		||||
                    _currentNotes.value = it
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (selectedCollection != null) {
 | 
			
		||||
                _pathStack.value = _pathStack.value + selectedCollection
 | 
			
		||||
            } else if (parentId == null) {
 | 
			
		||||
                _pathStack.value = emptyList()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createCollection(name: String) {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            val newCollection = NoteCollection(name = name, parentId = _currentParentId.value)
 | 
			
		||||
            collectionRepository.insertCollection(newCollection)
 | 
			
		||||
            loadContentForParent(_currentParentId.value)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun removeNote(noteEntity: NoteEntity) {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            noteRepository.delete(noteEntity)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun removeCollection(noteCollection: NoteCollection) {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            collectionRepository.deleteCollection(noteCollection)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun navigateToLevel(index: Int) {
 | 
			
		||||
        val newStack = _pathStack.value.take(index + 1)
 | 
			
		||||
        _pathStack.value = newStack
 | 
			
		||||
        val newParent = newStack.lastOrNull()?.id
 | 
			
		||||
        loadContentForParent(newParent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun navigateUp() {
 | 
			
		||||
        val newStack = _pathStack.value.dropLast(1)
 | 
			
		||||
        _pathStack.value = newStack
 | 
			
		||||
        val newParentId = newStack.lastOrNull()?.id
 | 
			
		||||
        loadContentForParent(newParentId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateCollection(collectionToEdit: NoteCollection, name: String) {
 | 
			
		||||
        if(name.isNotEmpty()) {
 | 
			
		||||
            collectionToEdit.name = name
 | 
			
		||||
            viewModelScope.launch {
 | 
			
		||||
                collectionRepository.updateCollection(collectionToEdit)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -61,7 +61,7 @@ class NoteViewModel(
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addNote(context: Context, title: String, composer: String?, year: Int?, genre: String?, description: String?, selectedUris: List<Uri>, onDone:() -> Unit) {
 | 
			
		||||
    fun addNote(context: Context, title: String, composer: String?, year: Int?, genre: String?, description: String?, selectedUris: List<Uri>, collectionID: Int?, onDone:() -> Unit) {
 | 
			
		||||
        viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
            val copiedUris = selectedUris.mapNotNull { uri ->
 | 
			
		||||
                try {
 | 
			
		||||
@ -94,7 +94,7 @@ class NoteViewModel(
 | 
			
		||||
                    genre = genre,
 | 
			
		||||
                    description = description,
 | 
			
		||||
                    imagePreview = preview_image_path.toString(),
 | 
			
		||||
                    collectionId = null
 | 
			
		||||
                    collectionId = collectionID
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                repository.insert(note)
 | 
			
		||||
 | 
			
		||||
@ -9,12 +9,14 @@ espressoCore = "3.6.1"
 | 
			
		||||
lifecycleRuntimeKtx = "2.8.7"
 | 
			
		||||
activityCompose = "1.8.0"
 | 
			
		||||
composeBom = "2024.04.01"
 | 
			
		||||
materialIconsExtended = "1.7.8"
 | 
			
		||||
navigationCompose = "2.8.9"
 | 
			
		||||
room = "2.7.1"
 | 
			
		||||
ksp="2.1.21-RC-2.0.0"
 | 
			
		||||
compose = "1.6.0" # oder was immer deine aktuelle Compose-Version ist
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
 | 
			
		||||
coil-compose = { group = "io.coil-kt", name = "coil-compose", version = "2.5.0" } # oder aktueller
 | 
			
		||||
compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "compose" }
 | 
			
		||||
lifecycle-livedata-ktx = "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user