Compare commits

..

No commits in common. "nextNoteVault" and "compose-integration" have entirely different histories.

24 changed files with 181 additions and 1461 deletions

1
.idea/.gitignore vendored
View File

@ -6,4 +6,3 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
/AndroidProjectSystem.xml

View File

@ -1,7 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="note_database" uuid="669634a7-d50e-4c04-b2fe-786d18bbd166">
<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">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
@ -23,12 +65,6 @@
<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>

View File

@ -4,7 +4,7 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-05-10T10:58:30.749991183Z">
<DropdownSelection timestamp="2025-04-29T17:33:41.575251940Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=R52N50NLGRT" />

View File

@ -40,9 +40,6 @@
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>

View File

@ -63,5 +63,4 @@ dependencies {
implementation(libs.compose.runtime.livedata)
implementation(libs.coil.compose)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.material.icons.extended)
}

View File

@ -2,82 +2,49 @@ package come.stormborntales.notevault
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
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.model.NoteEntry
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.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.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.InputStream
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val applicationContext = applicationContext
val database = AppDatabase.getDatabase(applicationContext)
val noteRepository = NoteRepository(database.noteDao())
val collectionRepository = CollectionRepository(database.collectionDao())
val viewModelFactory = NoteViewModelFactory(noteRepository)
val noteBrowserViewModelFactory = NoteBrowserViewModelFactory(noteRepository, collectionRepository)
val repository = NoteRepository(database.noteDao())
val viewModelFactory = NoteViewModelFactory(repository)
setContent {
NoteVaultTheme {
val navController = rememberNavController()
val viewModel: NoteViewModel = viewModel(factory = viewModelFactory)
val noteBrowserViewModel: NoteBrowserViewModel = viewModel(factory = noteBrowserViewModelFactory)
val context = LocalContext.current
// Globale Notenliste
val notes = remember { mutableStateListOf<NoteEntry>() }
// Bildauswahl + Dialog-States
var selectedUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
var showDialog by remember { mutableStateOf(false) }
var noteToEdit by remember { mutableStateOf<NoteEntity?>(null) }
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
// Launcher innerhalb von Compose
val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenMultipleDocuments(),
onResult = { uris ->
@ -88,176 +55,35 @@ class MainActivity : ComponentActivity() {
}
)
val openDialog: (NoteEntity?) -> Unit = { note ->
Log.d("EditNote", "NoteEntity: ${note?.title}")
noteToEdit = note
showDialog = true
}
val navItems = listOf(
"Home" to "main",
"Notes" to "notes",
"Einstellungen" to "settings"
// UI anzeigen
MainScreen(
viewModel = viewModel,
onAddNoteClicked = {
imagePickerLauncher.launch(
arrayOf("image/*")
)
}
)
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Text("Navigation", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleMedium)
navItems.forEach { (label, route) ->
NavigationDrawerItem(
label = { Text(label) },
selected = false,
onClick = {
navController.navigate(route) {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
scope.launch { drawerState.close() }
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}
) {
Scaffold(
topBar = {
var searchQuery by remember { mutableStateOf("") }
var isMenuExpanded by remember { mutableStateOf(false) }
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
TopAppBar(
title = {
if(currentRoute == "main") {
TextField(
value = searchQuery,
onValueChange = {
searchQuery = it
viewModel.searchQuery.value = it
},
placeholder = { Text("Suche...") },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(end = 48.dp), // Platz für Avatar
textStyle = MaterialTheme.typography.bodyLarge,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
)
)
} else {
Text("NoteVault")
}
},
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(Icons.Default.Menu, contentDescription = "Menü öffnen")
}
},
actions = {
Box {
IconButton(onClick = { isMenuExpanded = true }) {
Icon(
imageVector = Icons.Default.AccountCircle, // Avatar-Icon
contentDescription = "Benutzerprofil"
)
}
DropdownMenu(
expanded = isMenuExpanded,
onDismissRequest = { isMenuExpanded = false }
) {
DropdownMenuItem(
text = { Text("Profil") },
onClick = {
isMenuExpanded = false
// TODO: Navigiere ggf. zu Profilscreen
}
)
DropdownMenuItem(
text = { Text("Abmelden") },
onClick = {
isMenuExpanded = false
// TODO: Logout-Logik
}
)
}
}
}
// Dialog bei Bedarf
if (showDialog) {
val context = LocalContext.current;
val scope = rememberCoroutineScope();
AddNoteDialog(
onDismiss = { showDialog = false },
onSave = { title, composer, year, genre, description ->
viewModel.addNote(
context = context,
title = title,
composer = composer,
year = year,
genre = genre,
description = description,
selectedUris = selectedUris,
onDone = { showDialog = false }
)
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = "main",
modifier = Modifier.padding(innerPadding)
) {
composable("main") {
MainScreen(
viewModel = viewModel,
onAddNoteClicked = {
imagePickerLauncher.launch(arrayOf("image/*"))
},
onEditNote = openDialog
)
}
composable("settings") {
SettingsScreen()
}
composable("notes") {
NotesScreen(collectionRepository, noteRepository, onImportNotes = {
imagePickerLauncher.launch(arrayOf("image/*"))
}, noteBrowserViewModel,
onEditNote = {
noteToEdit = it
showDialog = true
})
}
}
if (showDialog) {
val context = LocalContext.current
AddNoteDialog(
onDismiss = { showDialog = false },
onSave = { title, composer, year, genre, description ->
if (noteToEdit == null) {
viewModel.addNote(
context = context,
title = title,
composer = composer,
year = year,
genre = genre,
description = description,
selectedUris = selectedUris,
collectionID = noteBrowserViewModel.currentParentId.value,
onDone = { showDialog = false }
)
} else {
viewModel.updateNote(
editedNote = noteToEdit!!,
updatedTitle = title,
updatedComposer = composer,
updatedYear = year,
updatedGenre = genre,
updatedDescription = description,
onDone = { showDialog = false }
)
}
},
initialTitle = noteToEdit?.title ?: "",
initialComposer = noteToEdit?.composer,
initialYear = noteToEdit?.year,
initialGenre = noteToEdit?.genre,
initialDescription = noteToEdit?.description
)
}
}
)
}
}
}

View File

@ -6,16 +6,13 @@ 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
@Database(entities = [NoteEntity::class, NoteCollection::class], version = 1)
@Database(entities = [NoteEntity::class], version = 1)
@TypeConverters(UriListConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
abstract fun collectionDao(): CollectionDao
companion object {
@Volatile

View File

@ -1,32 +0,0 @@
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

@ -5,7 +5,6 @@ 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.NoteEntity
import kotlinx.coroutines.flow.Flow
@ -19,9 +18,4 @@ interface NoteDao {
@Delete
suspend fun delete(note: NoteEntity)
@Update
suspend fun update(note: NoteEntity)
@Query("SELECT * FROM notes WHERE collectionId IS :collectionId")
fun getNotesForCollection(collectionId: Int?): Flow<List<NoteEntity>>
}

View File

@ -1,11 +0,0 @@
package come.stormborntales.notevault.data.local.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity("note_collections")
data class NoteCollection(
@PrimaryKey(autoGenerate = true) var id: Int = 0,
var name: String,
var parentId: Int? = null
)

View File

@ -1,24 +1,16 @@
package come.stormborntales.notevault.data.local.entity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "notes",
foreignKeys = [ForeignKey(
entity = NoteCollection::class,
parentColumns = ["id"],
childColumns = ["collectionId"],
onDelete = ForeignKey.CASCADE
)])
@Entity(tableName = "notes")
data class NoteEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
var title: String,
val title: String,
val images: List<String>, // oder String + TypeConverter
var composer: String?,
var year: Int?,
var genre: String?,
var description: String?,
val imagePreview: String,
val collectionId: Int?
)
val composer: String?,
val year: Int?,
val genre: String?,
val description: String?,
val imagePreview: String
)

View File

@ -1,34 +0,0 @@
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

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

View File

@ -14,22 +14,15 @@ import androidx.compose.ui.unit.dp
@Composable
fun AddNoteDialog(
onDismiss: () -> Unit,
onSave: (title: String, composer: String?, year: Int?, genre: String?, description: String?) -> Unit,
initialTitle: String = "",
initialComposer: String? = null,
initialYear: Int? = null,
initialGenre: String? = null,
initialDescription: String? = null,
collectionId: Int? = null
onSave: (title: String, composer: String?, year: Int?, genre: String?, description: String?) -> Unit
) {
var title by remember { mutableStateOf(initialTitle) }
var composer by remember { mutableStateOf(initialComposer ?: "") }
var yearText by remember { mutableStateOf(initialYear?.toString() ?: "") }
var genre by remember { mutableStateOf(initialGenre ?: "") }
var description by remember { mutableStateOf(initialDescription ?: "") }
var title by remember { mutableStateOf("") }
var composer by remember { mutableStateOf("") }
var yearText by remember { mutableStateOf("") }
var genre by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }
var showTitleError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
confirmButton = {

View File

@ -6,13 +6,10 @@ import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.media.Image
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@ -24,7 +21,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import come.stormborntales.notevault.FullscreenImageViewerActivity
@ -33,7 +29,6 @@ import come.stormborntales.notevault.data.model.NoteEntry
import come.stormborntales.notevault.ui.viewmodel.NoteViewModel
import java.io.InputStream
import androidx.core.net.toUri
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
fun loadImageBitmap(context: Context, uriString: String): ImageBitmap? {
@ -46,14 +41,12 @@ fun loadImageBitmap(context: Context, uriString: String): ImageBitmap? {
e.printStackTrace()
null
}
}@Composable
fun NoteCard(note: NoteEntity, onDeleteNote: (NoteEntity) -> Unit, onEditNote: (NoteEntity) -> Unit) {
}
@Composable
fun NoteCard(note: NoteEntity) {
val context = LocalContext.current
val screenWidth = LocalConfiguration.current.screenWidthDp // Bildschirmbreite in dp
// Dynamische Bildgröße basierend auf der Bildschirmbreite
val imageSize = if (screenWidth < 400) 80.dp else 120.dp
Card(
modifier = Modifier
@ -62,171 +55,79 @@ fun NoteCard(note: NoteEntity, onDeleteNote: (NoteEntity) -> Unit, onEditNote: (
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
) {
// Responsive Layout: Überprüfen, ob der Bildschirm schmaler als 360 dp ist
if (screenWidth < 400) {
// Wenn der Bildschirm schmal ist, arrangiere die Elemente vertikal
Row(modifier = Modifier.padding(16.dp)) {
AsyncImage(
model = note.imagePreview,
contentDescription = "Vorschaubild",
modifier = Modifier
.size(imageSize)
.clip(RoundedCornerShape(12.dp))
)
Row(modifier = Modifier.padding(16.dp)) {
// Linkes Vorschaubild
AsyncImage(
model = note.imagePreview,
contentDescription = "Vorschaubild",
modifier = Modifier
.size(120.dp)
.clip(RoundedCornerShape(12.dp))
)
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = note.title,
style = MaterialTheme.typography.titleMedium
)
note.composer?.let {
Text("von $it", style = MaterialTheme.typography.labelMedium)
}
Spacer(modifier = Modifier.height(4.dp))
note.year?.let {
Text("Jahr: $it", style = MaterialTheme.typography.bodySmall)
}
note.genre?.let {
Text("Genre: $it", style = MaterialTheme.typography.bodySmall)
}
note.description?.let {
Text("Beschreibung: $it", style = MaterialTheme.typography.bodySmall, maxLines = 2)
}
Spacer(modifier = Modifier.height(16.dp)) // Abstand zwischen Text und Buttons
}
}
// Buttons unter dem Text
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth().padding(16.dp),
// Rechte Info-Spalte
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
.padding(start=16.dp)
) {
OutlinedButton(
onClick = {
val uris = ArrayList<Uri>()
note.images.forEach { uris.add(it.toUri()) }
val intent = Intent(context, FullscreenImageViewerActivity::class.java).apply {
putParcelableArrayListExtra("imageUris", ArrayList(uris))
}
context.startActivity(intent)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Anzeigen", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onEditNote(note)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Bearbeiten", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onDeleteNote(note)
},
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Löschen", style = MaterialTheme.typography.labelLarge)
}
}
} else {
// Wenn der Bildschirm breiter als 360 dp ist, arrangiere die Elemente nebeneinander
Row(modifier = Modifier.padding(16.dp)) {
// Linkes Vorschaubild
AsyncImage(
model = note.imagePreview,
contentDescription = "Vorschaubild",
modifier = Modifier
.size(imageSize)
.clip(RoundedCornerShape(12.dp))
Text(
text = note.title,
style = MaterialTheme.typography.titleMedium
)
// Rechte Info-Spalte
Column(
modifier = Modifier
.weight(1f) // Damit die Spalte den verfügbaren Platz nutzt
.align(Alignment.CenterVertically)
.padding(start = 16.dp)
note.composer?.let {
Text("von $it", style = MaterialTheme.typography.labelMedium)
}
Spacer(modifier = Modifier.height(4.dp))
note.year?.let {
Text("Jahr: $it", style = MaterialTheme.typography.bodySmall)
}
note.genre?.let {
Text("Genre: $it", style = MaterialTheme.typography.bodySmall)
}
note.description?.let {
Text("Beschreibung: $it", style = MaterialTheme.typography.bodySmall, maxLines = 2)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = note.title,
style = MaterialTheme.typography.titleMedium
)
OutlinedButton(
onClick = {
val uris = ArrayList<Uri>();
note.images.forEach { uris.add(it.toUri()) }
note.composer?.let {
Text("von $it", style = MaterialTheme.typography.labelMedium)
}
Spacer(modifier = Modifier.height(4.dp))
note.year?.let {
Text("Jahr: $it", style = MaterialTheme.typography.bodySmall)
}
note.genre?.let {
Text("Genre: $it", style = MaterialTheme.typography.bodySmall)
}
note.description?.let {
Text("Beschreibung: $it", style = MaterialTheme.typography.bodySmall, maxLines = 2)
}
Spacer(modifier = Modifier.height(8.dp))
// Buttons in einer Row anordnen
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth() // Stellt sicher, dass die Row den gesamten verfügbaren Platz einnimmt
val intent = Intent(context, FullscreenImageViewerActivity::class.java).apply {
putParcelableArrayListExtra("imageUris", ArrayList(uris))
}
context.startActivity(intent)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
OutlinedButton(
onClick = {
val uris = ArrayList<Uri>()
note.images.forEach { uris.add(it.toUri()) }
val intent = Intent(context, FullscreenImageViewerActivity::class.java).apply {
putParcelableArrayListExtra("imageUris", ArrayList(uris))
}
context.startActivity(intent)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Anzeigen", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onEditNote(note)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Bearbeiten", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onDeleteNote(note)
},
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Löschen", style = MaterialTheme.typography.labelLarge)
}
Text("Anzeigen", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = { /* TODO */ },
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Bearbeiten", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = { /* TODO */ },
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Löschen", style = MaterialTheme.typography.labelLarge)
}
}
}
@ -239,11 +140,15 @@ fun NoteCard(note: NoteEntity, onDeleteNote: (NoteEntity) -> Unit, onEditNote: (
@Composable
fun MainScreen(
onAddNoteClicked: () -> Unit, // Übergib hier deine Logik für den Import
viewModel: NoteViewModel,
onEditNote: (NoteEntity) -> Unit
viewModel: NoteViewModel
) {
val notes by viewModel.filteredNotes.collectAsState(initial = emptyList())
val notes by viewModel.notes.observeAsState(emptyList())
Scaffold(
topBar = {
TopAppBar(
title = { Text("Meine Noten") }
)
},
floatingActionButton = {
FloatingActionButton(
onClick = onAddNoteClicked
@ -260,9 +165,7 @@ fun MainScreen(
.padding(16.dp)
) {
items(notes) { note ->
NoteCard(note = note, onDeleteNote = { noteEntity ->
viewModel.deleteNote(noteEntity)
}, onEditNote = onEditNote)
NoteCard(note = note)
}
}
}

View File

@ -1,79 +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 SettingsScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Einstellungen",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
Divider()
Spacer(modifier = Modifier.height(16.dp))
// Beispielhafte Einstellung
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Dark Mode",
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = false,
onCheckedChange = { /* TODO: Dark Mode aktivieren */ }
)
}
Spacer(modifier = Modifier.height(16.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Benachrichtigungen",
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = true,
onCheckedChange = { /* TODO: Benachrichtigungseinstellungen */ }
)
}
Spacer(modifier = Modifier.height(32.dp))
OutlinedButton(
onClick = { /* TODO: Impressum anzeigen */ },
modifier = Modifier.fillMaxWidth()
) {
Text("Impressum")
}
}
}

View File

@ -1,82 +0,0 @@
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")
}
}
)
}

View File

@ -1,150 +0,0 @@
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)
}
}
)
}
}
}
}
}

View File

@ -1,150 +0,0 @@
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)
}
}
)
}
}
}
}
}

View File

@ -1,305 +0,0 @@
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
)
}
}
)
}

View File

@ -1,103 +0,0 @@
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)
}
}
}
}

View File

@ -1,20 +0,0 @@
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")
}
}

View File

@ -14,35 +14,13 @@ import come.stormborntales.notevault.data.repository.NoteRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.InputStream
import androidx.core.graphics.scale
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
class NoteViewModel(
private val repository: NoteRepository,
) : ViewModel() {
// Sucheingabe als StateFlow
val searchQuery = MutableStateFlow("")
// Alle Notizen als Flow aus der Datenbank (NICHT blockierend!)
private val allNotes: Flow<List<NoteEntity>> = repository.allNotes
// Gefilterte Notizen basierend auf Sucheingabe
val filteredNotes: StateFlow<List<NoteEntity>> = combine(searchQuery, allNotes) { query, notes ->
if (query.isBlank()) {
notes
} else {
notes.filter {
it.title.contains(query, ignoreCase = true) ||
it.description?.contains(query, ignoreCase = true) == true
}
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val notes = repository.allNotes.asLiveData()
fun createPreviewImage(context: Context, uri: Uri): File? {
return try {
@ -61,7 +39,7 @@ class NoteViewModel(
}
}
fun addNote(context: Context, title: String, composer: String?, year: Int?, genre: String?, description: String?, selectedUris: List<Uri>, collectionID: Int?, onDone:() -> Unit) {
fun addNote(context: Context, title: String, composer: String?, year: Int?, genre: String?, description: String?, selectedUris: List<Uri>, onDone:() -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val copiedUris = selectedUris.mapNotNull { uri ->
try {
@ -93,8 +71,7 @@ class NoteViewModel(
year = year,
genre = genre,
description = description,
imagePreview = preview_image_path.toString(),
collectionId = collectionID
imagePreview = preview_image_path.toString()
)
repository.insert(note)
@ -109,26 +86,4 @@ class NoteViewModel(
repository.delete(note)
}
}
fun updateNote(
editedNote: NoteEntity,
updatedTitle: String,
updatedComposer: String?,
updatedYear: Int?,
updatedGenre: String?,
updatedDescription: String?,
onDone: () -> Unit
) {
viewModelScope.launch {
editedNote.title = updatedTitle
editedNote.year = updatedYear;
editedNote.composer = updatedComposer
editedNote.genre = updatedGenre
editedNote.description = updatedDescription
repository.update(editedNote)
onDone()
}
}
}

View File

@ -9,14 +9,12 @@ 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"