Berikan fungsi pada fitur pencarian pada halaman file berbintang, arsip, dan sampah

This commit is contained in:
202310715082 FAZRI ABDURRAHMAN 2025-12-25 01:35:21 +07:00
parent 2e3cb39244
commit f0e4396da1
3 changed files with 347 additions and 74 deletions

View File

@ -1,12 +1,30 @@
package com.example.notesai.presentation.screens.archive package com.example.notesai.presentation.screens.archive
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Archive import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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
@ -20,18 +38,90 @@ fun ArchiveScreen(
onRestore: (Note) -> Unit, onRestore: (Note) -> Unit,
onDelete: (Note) -> Unit onDelete: (Note) -> Unit
) { ) {
if (notes.isEmpty()) { var searchQuery by remember { mutableStateOf("") }
// Filter berdasarkan search query
val filteredNotes = if (searchQuery.isBlank()) {
notes
} else {
notes.filter { note ->
note.title.contains(searchQuery, ignoreCase = true) ||
note.content.contains(searchQuery, ignoreCase = true) ||
note.description.contains(searchQuery, ignoreCase = true) ||
categories.find { it.id == note.categoryId }?.name?.contains(searchQuery, ignoreCase = true) == true
}
}
Column(modifier = Modifier.fillMaxSize()) {
// Search Bar
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
placeholder = {
Text(
"Cari catatan arsip...",
color = Color(0xFF64748B)
)
},
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = null,
tint = Color(0xFF64748B)
)
},
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { searchQuery = "" }) {
Icon(
Icons.Default.Clear,
contentDescription = "Clear",
tint = Color(0xFF64748B)
)
}
}
},
shape = RoundedCornerShape(12.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color(0xFF1E293B),
unfocusedContainerColor = Color(0xFF1E293B),
focusedBorderColor = Color(0xFF6366F1),
unfocusedBorderColor = Color(0xFF334155),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color(0xFF6366F1)
),
singleLine = true
)
// Content
if (filteredNotes.isEmpty()) {
if (searchQuery.isNotEmpty()) {
EmptyState(
icon = Icons.Default.Search,
message = "Tidak ada hasil",
subtitle = "Coba kata kunci lain"
)
} else {
EmptyState( EmptyState(
icon = Icons.Default.Archive, icon = Icons.Default.Archive,
message = "Arsip kosong", message = "Arsip kosong",
subtitle = "Catatan yang diarsipkan akan muncul di sini" subtitle = "Catatan yang diarsipkan akan muncul di sini"
) )
}
} else { } else {
LazyColumn( LazyColumn(
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 100.dp
),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items(notes) { note -> items(filteredNotes) { note ->
val category = categories.find { it.id == note.categoryId } val category = categories.find { it.id == note.categoryId }
ArchiveNoteCard( ArchiveNoteCard(
note = note, note = note,
@ -42,4 +132,5 @@ fun ArchiveScreen(
} }
} }
} }
}
} }

View File

@ -3,14 +3,32 @@ package com.example.notesai.presentation.screens.starred
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.graphics.Color
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.starred.components.StarredNoteCard import com.example.notesai.presentation.screens.starred.components.StarredNoteCard
@ -25,27 +43,93 @@ fun StarredNotesScreen(
onNoteClick: (Note) -> Unit, onNoteClick: (Note) -> Unit,
onUnpin: (Note) -> Unit onUnpin: (Note) -> Unit
) { ) {
val starredNotes = notes.filter { it.isPinned && !it.isArchived && !it.isDeleted } var searchQuery by remember { mutableStateOf("") }
val starredNotes = notes
.filter { it.isPinned && !it.isArchived && !it.isDeleted }
.sortedByDescending { it.timestamp } .sortedByDescending { it.timestamp }
// Filter berdasarkan search query
val filteredNotes = if (searchQuery.isBlank()) {
starredNotes
} else {
starredNotes.filter { note ->
note.title.contains(searchQuery, ignoreCase = true) ||
note.content.contains(searchQuery, ignoreCase = true) ||
note.description.contains(searchQuery, ignoreCase = true)
}
}
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
if (starredNotes.isEmpty()) { // Search Bar
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
placeholder = {
Text(
"Cari catatan berbintang...",
color = Color(0xFF64748B)
)
},
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = null,
tint = Color(0xFF64748B)
)
},
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { searchQuery = "" }) {
Icon(
Icons.Default.Clear,
contentDescription = "Clear",
tint = Color(0xFF64748B)
)
}
}
},
shape = RoundedCornerShape(12.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color(0xFF1E293B),
unfocusedContainerColor = Color(0xFF1E293B),
focusedBorderColor = Color(0xFF6366F1),
unfocusedBorderColor = Color(0xFF334155),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color(0xFF6366F1)
),
singleLine = true
)
// Content
if (filteredNotes.isEmpty()) {
if (searchQuery.isNotEmpty()) {
EmptyState(
icon = Icons.Default.Search,
message = "Tidak ada hasil",
subtitle = "Coba kata kunci lain"
)
} else {
EmptyState( EmptyState(
icon = Icons.Default.Star, icon = Icons.Default.Star,
message = "Belum ada catatan berbintang", message = "Belum ada catatan berbintang",
subtitle = "Catatan yang ditandai berbintang akan muncul di sini" subtitle = "Catatan yang ditandai berbintang akan muncul di sini"
) )
}
} else { } else {
LazyColumn( LazyColumn(
contentPadding = PaddingValues( contentPadding = PaddingValues(
start = 16.dp, start = 16.dp,
end = 16.dp, end = 16.dp,
top = 16.dp, bottom = 100.dp
bottom = 100.dp // Extra space untuk bottom bar
), ),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items(starredNotes) { note -> items(filteredNotes) { note ->
val category = categories.find { it.id == note.categoryId } val category = categories.find { it.id == note.categoryId }
StarredNoteCard( StarredNoteCard(
note = note, note = note,

View File

@ -1,15 +1,30 @@
// 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
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -28,33 +43,114 @@ fun TrashScreen(
onRestoreCategory: (Category) -> Unit, onRestoreCategory: (Category) -> Unit,
onDeleteCategoryPermanent: (Category) -> Unit onDeleteCategoryPermanent: (Category) -> Unit
) { ) {
var searchQuery by remember { mutableStateOf("") }
// Filter kategori dan note yang dihapus // Filter kategori dan note yang dihapus
val deletedCategories = categories.filter { it.isDeleted } val deletedCategories = categories.filter { it.isDeleted }
val deletedNotes = notes.filter { it.isDeleted } val deletedNotes = notes.filter { it.isDeleted }
if (deletedCategories.isEmpty() && deletedNotes.isEmpty()) { // Filter berdasarkan search query
val filteredCategories = if (searchQuery.isBlank()) {
deletedCategories
} else {
deletedCategories.filter { category ->
category.name.contains(searchQuery, ignoreCase = true)
}
}
val filteredNotes = if (searchQuery.isBlank()) {
deletedNotes
} else {
deletedNotes.filter { note ->
note.title.contains(searchQuery, ignoreCase = true) ||
note.content.contains(searchQuery, ignoreCase = true) ||
note.description.contains(searchQuery, ignoreCase = true) ||
categories.find { it.id == note.categoryId }?.name?.contains(searchQuery, ignoreCase = true) == true
}
}
Column(modifier = Modifier.fillMaxSize()) {
// Search Bar
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
placeholder = {
Text(
"Cari di sampah...",
color = Color(0xFF64748B)
)
},
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = null,
tint = Color(0xFF64748B)
)
},
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { searchQuery = "" }) {
Icon(
Icons.Default.Clear,
contentDescription = "Clear",
tint = Color(0xFF64748B)
)
}
}
},
shape = RoundedCornerShape(12.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color(0xFF1E293B),
unfocusedContainerColor = Color(0xFF1E293B),
focusedBorderColor = Color(0xFF6366F1),
unfocusedBorderColor = Color(0xFF334155),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color(0xFF6366F1)
),
singleLine = true
)
// Content
if (filteredCategories.isEmpty() && filteredNotes.isEmpty()) {
if (searchQuery.isNotEmpty()) {
EmptyState(
icon = Icons.Default.Search,
message = "Tidak ada hasil",
subtitle = "Coba kata kunci lain"
)
} else {
EmptyState( EmptyState(
icon = Icons.Default.Delete, icon = Icons.Default.Delete,
message = "Sampah kosong", message = "Sampah kosong",
subtitle = "Kategori dan 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(
start = 16.dp,
end = 16.dp,
bottom = 100.dp
),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
// Section: Kategori Terhapus // Section: Kategori Terhapus
if (deletedCategories.isNotEmpty()) { if (filteredCategories.isNotEmpty()) {
item { item {
Text( Text(
"Kategori Terhapus (${deletedCategories.size})", "Kategori Terhapus (${filteredCategories.size})",
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color(0xFF94A3B8) color = Color(0xFF94A3B8),
modifier = Modifier.padding(vertical = 8.dp)
) )
} }
items(deletedCategories) { category -> items(filteredCategories) { category ->
val notesInCategory = notes.count { val notesInCategory = notes.count {
it.categoryId == category.id && it.isDeleted it.categoryId == category.id && it.isDeleted
} }
@ -68,17 +164,18 @@ fun TrashScreen(
} }
// Section: Catatan Terhapus // Section: Catatan Terhapus
if (deletedNotes.isNotEmpty()) { if (filteredNotes.isNotEmpty()) {
item { item {
Text( Text(
"Catatan Terhapus (${deletedNotes.size})", "Catatan Terhapus (${filteredNotes.size})",
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color(0xFF94A3B8) color = Color(0xFF94A3B8),
modifier = Modifier.padding(vertical = 8.dp)
) )
} }
items(deletedNotes) { note -> items(filteredNotes) { note ->
val category = categories.find { it.id == note.categoryId } val category = categories.find { it.id == note.categoryId }
TrashNoteCard( TrashNoteCard(
note = note, note = note,
@ -90,4 +187,5 @@ fun TrashScreen(
} }
} }
} }
}
} }