nextNoteVault #23

Merged
sebastian merged 57 commits from nextNoteVault into master 2025-05-10 06:23:23 +00:00
11 changed files with 205 additions and 39 deletions
Showing only changes of commit ba0cbcef09 - Show all commits

View File

@ -4,7 +4,7 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-04-29T15:21:34.999046322Z">
<DropdownSelection timestamp="2025-04-29T16:02:42.921287190Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=R52N50NLGRT" />

View File

@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp) // NEU
}
android {
@ -56,4 +57,9 @@ dependencies {
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.foundation)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.lifecycle.livedata.ktx)
ksp(libs.androidx.room.compiler)
}

View File

@ -10,10 +10,15 @@ import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import come.stormborntales.notevault.data.local.AppDatabase
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.theme.NoteVaultTheme
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
@ -22,9 +27,14 @@ import java.io.InputStream
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val applicationContext = applicationContext
val database = AppDatabase.getDatabase(applicationContext)
val repository = NoteRepository(database.noteDao())
val viewModelFactory = NoteViewModelFactory(repository)
setContent {
NoteVaultTheme {
val viewModel: NoteViewModel = viewModel(factory = viewModelFactory)
val context = LocalContext.current
// Globale Notenliste
@ -62,43 +72,16 @@ class MainActivity : ComponentActivity() {
AddNoteDialog(
onDismiss = { showDialog = false },
onSave = { title, composer, year, genre, description ->
scope.launch(Dispatchers.IO) {
val copiedUris = selectedUris.mapNotNull { uri ->
try {
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val extension = MimeTypeMap.getSingleton()
.getExtensionFromMimeType(context.contentResolver.getType(uri)) ?: "jpg" // Fallback
val outputFile = File(context.filesDir, "note_${System.currentTimeMillis()}.$extension")
inputStream?.use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
Uri.fromFile(outputFile)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
if (copiedUris.isNotEmpty()) {
notes.add(
NoteEntry(
title = title,
images = copiedUris,
composer = composer,
year = year,
genre = genre,
description = description
)
)
}
showDialog = false
}
viewModel.addNote(
context = context,
title = title,
composer = composer,
year = year,
genre = genre,
description = description,
selectedUris = selectedUris,
onDone = { showDialog = false }
)
}
)
}

View File

@ -0,0 +1,32 @@
package come.stormborntales.notevault.data.local
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import come.stormborntales.notevault.data.local.dao.NoteDao
import come.stormborntales.notevault.data.local.entity.NoteEntity
@Database(entities = [NoteEntity::class], version = 1)
@TypeConverters(UriListConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"note_database"
).build()
INSTANCE = instance
instance
}
}
}
}

View File

@ -0,0 +1,11 @@
package come.stormborntales.notevault.data.local
import androidx.room.TypeConverter
class UriListConverter {
@TypeConverter
fun fromList(list: List<String>): String = list.joinToString(",")
@TypeConverter
fun toList(data: String): List<String> = if (data.isBlank()) emptyList() else data.split(",")
}

View File

@ -0,0 +1,21 @@
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 come.stormborntales.notevault.data.local.entity.NoteEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface NoteDao {
@Query("SELECT * FROM notes")
fun getAll(): Flow<List<NoteEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(note: NoteEntity)
@Delete
suspend fun delete(note: NoteEntity)
}

View File

@ -0,0 +1,15 @@
package come.stormborntales.notevault.data.local.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "notes")
data class NoteEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val images: List<String>, // oder String + TypeConverter
val composer: String?,
val year: Int?,
val genre: String?,
val description: String?
)

View File

@ -0,0 +1,12 @@
package come.stormborntales.notevault.data.repository
import come.stormborntales.notevault.data.local.dao.NoteDao
import come.stormborntales.notevault.data.local.entity.NoteEntity
import kotlinx.coroutines.flow.Flow
class NoteRepository(private val dao: NoteDao) {
val allNotes: Flow<List<NoteEntity>> = dao.getAll()
suspend fun insert(note: NoteEntity) = dao.insert(note)
suspend fun delete(note: NoteEntity) = dao.delete(note)
}

View File

@ -0,0 +1,62 @@
package come.stormborntales.notevault.ui.viewmodel
import android.content.Context
import android.net.Uri
import android.webkit.MimeTypeMap
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import come.stormborntales.notevault.data.local.entity.NoteEntity
import come.stormborntales.notevault.data.repository.NoteRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class NoteViewModel(
private val repository: NoteRepository,
) : ViewModel() {
val notes = repository.allNotes.asLiveData()
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 {
val inputStream = context.contentResolver.openInputStream(uri)
val extension = MimeTypeMap.getSingleton()
.getExtensionFromMimeType(context.contentResolver.getType(uri)) ?: "jpg"
val outputFile = File(context.filesDir, "note_${System.currentTimeMillis()}.$extension")
inputStream?.use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
Uri.fromFile(outputFile).toString() // speichern als String
} catch (e: Exception) {
e.printStackTrace()
null
}
}
if (copiedUris.isNotEmpty()) {
val note = NoteEntity(
title = title,
images = copiedUris, // muss als List<String> gespeichert sein
composer = composer,
year = year,
genre = genre,
description = description
)
repository.insert(note)
}
onDone()
}
}
fun deleteNote(note: NoteEntity) {
viewModelScope.launch {
repository.delete(note)
}
}
}

View File

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

View File

@ -10,13 +10,17 @@ lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.8.0"
composeBom = "2024.04.01"
navigationCompose = "2.8.9"
room = "2.7.1"
ksp="2.1.21-RC-2.0.0"
[libraries]
lifecycle-livedata-ktx = "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
androidx-compose-bom-v20240100 = { module = "androidx.compose:compose-bom", version.ref = "composeBomVersion" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-foundation = { module = "androidx.compose.foundation:foundation" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@ -30,9 +34,12 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }