Menyesuaikan Fitur edit dan hapus pada kategori

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-12-13 15:55:28 +07:00
parent 3f84068d72
commit 4b9cdcbb13
5 changed files with 347 additions and 39 deletions

View File

@ -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 }
}
) )
} }
} }

View File

@ -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
) )

View File

@ -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
)

View File

@ -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) }
) )
} }
} }
} }
} }
}

View File

@ -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)
}
}
}
}
}