Menyesuaikan Fitur edit dan hapus pada kategori
This commit is contained in:
parent
3f84068d72
commit
4b9cdcbb13
@ -255,7 +255,7 @@ fun NotesApp() {
|
|||||||
) {
|
) {
|
||||||
when (currentScreen) {
|
when (currentScreen) {
|
||||||
"main" -> MainScreen(
|
"main" -> MainScreen(
|
||||||
categories = categories,
|
categories = categories.filter { !it.isDeleted }, // TAMBAHKAN FILTER INI
|
||||||
notes = notes,
|
notes = notes,
|
||||||
selectedCategory = selectedCategory,
|
selectedCategory = selectedCategory,
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
@ -271,28 +271,69 @@ fun NotesApp() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCategoryDelete = { category ->
|
onCategoryDelete = { category ->
|
||||||
// Delete kategori dan semua catatan di dalamnya
|
// UBAH: Jangan filter, tapi set isDeleted = true
|
||||||
categories = categories.filter { it.id != category.id }
|
categories = categories.map {
|
||||||
notes = notes.filter { it.categoryId != category.id }
|
if (it.id == category.id) it.copy(isDeleted = true)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
// Note di dalam kategori juga di-delete
|
||||||
|
notes = notes.map {
|
||||||
|
if (it.categoryId == category.id) it.copy(isDeleted = true)
|
||||||
|
else it
|
||||||
|
}
|
||||||
selectedCategory = null
|
selectedCategory = null
|
||||||
},
|
},
|
||||||
onCategoryEdit = { category, newName, newGradientStart, newGradientEnd ->
|
onCategoryEdit = { category, newName, newGradientStart, newGradientEnd ->
|
||||||
categories = categories.updateWhere(
|
categories = categories.map {
|
||||||
predicate = { it.id == category.id },
|
if (it.id == category.id) {
|
||||||
transform = {
|
|
||||||
it.copy(
|
it.copy(
|
||||||
name = newName,
|
name = newName,
|
||||||
gradientStart = newGradientStart,
|
gradientStart = newGradientStart,
|
||||||
gradientEnd = newGradientEnd,
|
gradientEnd = newGradientEnd,
|
||||||
timestamp = System.currentTimeMillis()
|
timestamp = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
"trash" -> TrashScreen(
|
||||||
|
notes = notes.filter { it.isDeleted },
|
||||||
|
categories = categories, // Pass semua categories (sudah ada yang isDeleted)
|
||||||
|
onRestoreNote = { note ->
|
||||||
|
notes = notes.map {
|
||||||
|
if (it.id == note.id) it.copy(isDeleted = false, isArchived = false)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDeleteNotePermanent = { note ->
|
||||||
|
notes = notes.filter { it.id != note.id }
|
||||||
|
},
|
||||||
|
onRestoreCategory = { category ->
|
||||||
|
// Restore kategori
|
||||||
|
categories = categories.map {
|
||||||
|
if (it.id == category.id) it.copy(isDeleted = false)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
// Restore semua note di dalam kategori
|
||||||
|
notes = notes.map {
|
||||||
|
if (it.categoryId == category.id) it.copy(isDeleted = false, isArchived = false)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDeleteCategoryPermanent = { category ->
|
||||||
|
// Hapus kategori permanen
|
||||||
|
categories = categories.filter { it.id != category.id }
|
||||||
|
// Hapus semua note di dalam kategori permanen
|
||||||
|
notes = notes.filter { it.categoryId != category.id }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
"starred" -> StarredNotesScreen(
|
"starred" -> StarredNotesScreen(
|
||||||
notes = notes,
|
notes = notes,
|
||||||
categories = categories,
|
categories = categories.filter { !it.isDeleted }, // FILTER
|
||||||
onNoteClick = { note ->
|
onNoteClick = { note ->
|
||||||
fullScreenNote = note
|
fullScreenNote = note
|
||||||
showFullScreenNote = true
|
showFullScreenNote = true
|
||||||
@ -306,13 +347,10 @@ fun NotesApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
"ai" -> AIHelperScreen(
|
|
||||||
categories = categories,
|
|
||||||
notes = notes.filter { !it.isDeleted }
|
|
||||||
)
|
|
||||||
"archive" -> ArchiveScreen(
|
"archive" -> ArchiveScreen(
|
||||||
notes = notes.filter { it.isArchived && !it.isDeleted },
|
notes = notes.filter { it.isArchived && !it.isDeleted },
|
||||||
categories = categories,
|
categories = categories.filter { !it.isDeleted }, // FILTER
|
||||||
onRestore = { note ->
|
onRestore = { note ->
|
||||||
notes = notes.map {
|
notes = notes.map {
|
||||||
if (it.id == note.id) it.copy(isArchived = false)
|
if (it.id == note.id) it.copy(isArchived = false)
|
||||||
@ -326,18 +364,10 @@ fun NotesApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
"trash" -> TrashScreen(
|
|
||||||
notes = notes.filter { it.isDeleted },
|
"ai" -> AIHelperScreen(
|
||||||
categories = categories,
|
categories = categories.filter { !it.isDeleted }, // FILTER
|
||||||
onRestore = { note ->
|
notes = notes.filter { !it.isDeleted }
|
||||||
notes = notes.map {
|
|
||||||
if (it.id == note.id) it.copy(isDeleted = false, isArchived = false)
|
|
||||||
else it
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDeletePermanent = { note ->
|
|
||||||
notes = notes.filter { it.id != note.id }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,5 +8,6 @@ data class Category(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val gradientStart: Long,
|
val gradientStart: Long,
|
||||||
val gradientEnd: Long,
|
val gradientEnd: Long,
|
||||||
val timestamp: Long = System.currentTimeMillis()
|
val timestamp: Long = System.currentTimeMillis(),
|
||||||
|
val isDeleted: Boolean = false // TAMBAHKAN INI
|
||||||
)
|
)
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.example.notesai.data.model
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
@Serializable
|
||||||
|
data class SerializableCategory(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val gradientStart: Long,
|
||||||
|
val gradientEnd: Long,
|
||||||
|
val timestamp: Long,
|
||||||
|
val isDeleted: Boolean = false // TAMBAHKAN INI
|
||||||
|
)
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
@Serializable
|
||||||
|
data class SerializableNote(
|
||||||
|
val id: String,
|
||||||
|
val categoryId: String,
|
||||||
|
val title: String,
|
||||||
|
val content: String,
|
||||||
|
val timestamp: Long,
|
||||||
|
val isArchived: Boolean,
|
||||||
|
val isDeleted: Boolean,
|
||||||
|
val isPinned: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
// Extension functions untuk konversi
|
||||||
|
fun Category.toSerializable() = SerializableCategory(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
gradientStart = gradientStart,
|
||||||
|
gradientEnd = gradientEnd,
|
||||||
|
timestamp = timestamp,
|
||||||
|
isDeleted = isDeleted // TAMBAHKAN INI
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SerializableCategory.toCategory() = Category(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
gradientStart = gradientStart,
|
||||||
|
gradientEnd = gradientEnd,
|
||||||
|
timestamp = timestamp,
|
||||||
|
isDeleted = isDeleted // TAMBAHKAN INI
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Note.toSerializable() = SerializableNote(
|
||||||
|
id = id,
|
||||||
|
categoryId = categoryId,
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
timestamp = timestamp,
|
||||||
|
isArchived = isArchived,
|
||||||
|
isDeleted = isDeleted,
|
||||||
|
isPinned = isPinned
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SerializableNote.toNote() = Note(
|
||||||
|
id = id,
|
||||||
|
categoryId = categoryId,
|
||||||
|
title = title,
|
||||||
|
content = content,
|
||||||
|
timestamp = timestamp,
|
||||||
|
isArchived = isArchived,
|
||||||
|
isDeleted = isDeleted,
|
||||||
|
isPinned = isPinned
|
||||||
|
)
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
// File: presentation/screens/trash/TrashScreen.kt
|
||||||
package com.example.notesai.presentation.screens.trash
|
package com.example.notesai.presentation.screens.trash
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -6,10 +7,15 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.notesai.presentation.components.EmptyState
|
import com.example.notesai.presentation.components.EmptyState
|
||||||
import com.example.notesai.presentation.screens.trash.components.TrashNoteCard
|
import com.example.notesai.presentation.screens.trash.components.TrashNoteCard
|
||||||
|
import com.example.notesai.presentation.screens.trash.components.TrashCategoryCard
|
||||||
import com.example.notesai.data.model.Note
|
import com.example.notesai.data.model.Note
|
||||||
import com.example.notesai.data.model.Category
|
import com.example.notesai.data.model.Category
|
||||||
|
|
||||||
@ -17,29 +23,71 @@ import com.example.notesai.data.model.Category
|
|||||||
fun TrashScreen(
|
fun TrashScreen(
|
||||||
notes: List<Note>,
|
notes: List<Note>,
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
onRestore: (Note) -> Unit,
|
onRestoreNote: (Note) -> Unit,
|
||||||
onDeletePermanent: (Note) -> Unit
|
onDeleteNotePermanent: (Note) -> Unit,
|
||||||
|
onRestoreCategory: (Category) -> Unit,
|
||||||
|
onDeleteCategoryPermanent: (Category) -> Unit
|
||||||
) {
|
) {
|
||||||
if (notes.isEmpty()) {
|
// Filter kategori dan note yang dihapus
|
||||||
|
val deletedCategories = categories.filter { it.isDeleted }
|
||||||
|
val deletedNotes = notes.filter { it.isDeleted }
|
||||||
|
|
||||||
|
if (deletedCategories.isEmpty() && deletedNotes.isEmpty()) {
|
||||||
EmptyState(
|
EmptyState(
|
||||||
icon = Icons.Default.Delete,
|
icon = Icons.Default.Delete,
|
||||||
message = "Sampah kosong",
|
message = "Sampah kosong",
|
||||||
subtitle = "Catatan yang dihapus akan muncul di sini"
|
subtitle = "Kategori dan catatan yang dihapus akan muncul di sini"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
items(notes) { note ->
|
// Section: Kategori Terhapus
|
||||||
|
if (deletedCategories.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
"Kategori Terhapus (${deletedCategories.size})",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color(0xFF94A3B8)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(deletedCategories) { category ->
|
||||||
|
val notesInCategory = notes.count {
|
||||||
|
it.categoryId == category.id && it.isDeleted
|
||||||
|
}
|
||||||
|
TrashCategoryCard(
|
||||||
|
category = category,
|
||||||
|
noteCount = notesInCategory,
|
||||||
|
onRestore = { onRestoreCategory(category) },
|
||||||
|
onDeletePermanent = { onDeleteCategoryPermanent(category) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section: Catatan Terhapus
|
||||||
|
if (deletedNotes.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
"Catatan Terhapus (${deletedNotes.size})",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color(0xFF94A3B8)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(deletedNotes) { note ->
|
||||||
val category = categories.find { it.id == note.categoryId }
|
val category = categories.find { it.id == note.categoryId }
|
||||||
TrashNoteCard(
|
TrashNoteCard(
|
||||||
note = note,
|
note = note,
|
||||||
categoryName = category?.name ?: "Unknown",
|
categoryName = category?.name ?: "Unknown",
|
||||||
onRestore = { onRestore(note) },
|
onRestore = { onRestoreNote(note) },
|
||||||
onDeletePermanent = { onDeletePermanent(note) }
|
onDeletePermanent = { onDeleteNotePermanent(note) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
// File: presentation/screens/trash/components/TrashCategoryCard.kt
|
||||||
|
package com.example.notesai.presentation.screens.trash.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Folder
|
||||||
|
import androidx.compose.material.icons.filled.Restore
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.example.notesai.data.model.Category
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TrashCategoryCard(
|
||||||
|
category: Category,
|
||||||
|
noteCount: Int,
|
||||||
|
onRestore: () -> Unit,
|
||||||
|
onDeletePermanent: () -> Unit
|
||||||
|
) {
|
||||||
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Dialog konfirmasi hapus permanen
|
||||||
|
if (showDeleteDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDeleteDialog = false },
|
||||||
|
title = { Text("Hapus Kategori Permanen?", color = Color.White) },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
"Kategori '${category.name}' dan $noteCount catatan di dalamnya akan dihapus permanen. Tindakan ini tidak dapat dibatalkan!",
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onDeletePermanent()
|
||||||
|
showDeleteDialog = false
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color(0xFFEF4444)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Hapus Permanen", color = Color.White)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Button(
|
||||||
|
onClick = { showDeleteDialog = false },
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color(0xFF64748B)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Batal", color = Color.White)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = Color(0xFF1E293B)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color(0xFF1E293B)
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
// Header dengan gradient
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(category.gradientStart).copy(alpha = 0.3f),
|
||||||
|
Color(category.gradientEnd).copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Folder,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White.copy(0.9f),
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
category.name,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
"$noteCount catatan",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.White.copy(0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Info
|
||||||
|
Text(
|
||||||
|
"Kategori yang dihapus",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color(0xFF64748B)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
TextButton(onClick = onRestore) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Restore,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = Color(0xFF10B981)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text("Pulihkan", color = Color(0xFF10B981), fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
TextButton(onClick = { showDeleteDialog = true }) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Delete,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = Color(0xFFEF4444)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text("Hapus Permanen", color = Color(0xFFEF4444), fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user