Menyesuaikan Tampilan Drawer Menu dan Halaman Catatan Berbintang

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-12-11 15:47:41 +07:00
parent 9c699767e9
commit f2da1792b1

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -50,7 +51,10 @@ import com.google.ai.client.generativeai.type.generationConfig
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.zIndex
import kotlinx.coroutines.delay
// Data Classes
@ -127,7 +131,7 @@ fun NotesApp() {
var showFullScreenNote by remember { mutableStateOf(false) }
var fullScreenNote by remember { mutableStateOf<Note?>(null) }
// Load data dari DataStore - dengan error handling
// Load data dari DataStore
LaunchedEffect(Unit) {
try {
dataStoreManager.categoriesFlow.collect { loadedCategories ->
@ -172,268 +176,292 @@ fun NotesApp() {
}
}
Scaffold(
topBar = {
if (!showFullScreenNote) {
ModernTopBar(
title = when(currentScreen) {
"main" -> if (selectedCategory != null) selectedCategory!!.name else "AI Notes"
"ai" -> "AI Helper"
"archive" -> "Arsip"
"trash" -> "Sampah"
else -> "AI Notes"
},
showBackButton = (selectedCategory != null && currentScreen == "main") || currentScreen == "ai",
onBackClick = {
if (currentScreen == "ai") {
currentScreen = "main"
} else {
selectedCategory = null
}
},
onMenuClick = { drawerState = !drawerState },
onSearchClick = { showSearch = !showSearch },
searchQuery = searchQuery,
onSearchQueryChange = { searchQuery = it },
showSearch = showSearch && currentScreen == "main"
)
}
},
floatingActionButton = {
AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
FloatingActionButton(
onClick = {
if (selectedCategory != null) {
editingNote = null
showNoteDialog = true
} else {
showCategoryDialog = true
}
},
containerColor = Color.Transparent,
modifier = Modifier
.shadow(8.dp, CircleShape)
.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
),
shape = CircleShape
)
Box(modifier = Modifier.fillMaxSize()) {
Scaffold(
topBar = {
if (!showFullScreenNote) {
ModernTopBar(
title = when(currentScreen) {
"main" -> if (selectedCategory != null) selectedCategory!!.name else "AI Notes"
"ai" -> "AI Helper"
"starred" -> "Berbintang"
"archive" -> "Arsip"
"trash" -> "Sampah"
else -> "AI Notes"
},
showBackButton = (selectedCategory != null && currentScreen == "main") || currentScreen == "ai" || currentScreen == "starred",
onBackClick = {
if (currentScreen == "ai" || currentScreen == "starred") {
currentScreen = "main"
} else {
selectedCategory = null
}
},
onMenuClick = { drawerState = !drawerState },
onSearchClick = { showSearch = !showSearch },
searchQuery = searchQuery,
onSearchQueryChange = { searchQuery = it },
showSearch = showSearch && currentScreen == "main"
)
}
},
floatingActionButton = {
AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
Icon(
Icons.Default.Add,
contentDescription = if (selectedCategory != null) "Tambah Catatan" else "Tambah Kategori",
tint = Color.White
FloatingActionButton(
onClick = {
if (selectedCategory != null) {
editingNote = null
showNoteDialog = true
} else {
showCategoryDialog = true
}
},
containerColor = Color.Transparent,
modifier = Modifier
.shadow(8.dp, CircleShape)
.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
),
shape = CircleShape
)
) {
Icon(
Icons.Default.Add,
contentDescription = if (selectedCategory != null) "Tambah Catatan" else "Tambah Kategori",
tint = Color.White
)
}
}
},
bottomBar = {
if (!showFullScreenNote) {
ModernBottomBar(
currentScreen = currentScreen,
onHomeClick = {
currentScreen = "main"
selectedCategory = null
},
onAIClick = { currentScreen = "ai" }
)
}
}
},
bottomBar = {
if (!showFullScreenNote) {
ModernBottomBar(
currentScreen = currentScreen,
onHomeClick = {
currentScreen = "main"
selectedCategory = null
},
onAIClick = { currentScreen = "ai" }
)
}
}
) { padding ->
Box(modifier = Modifier.fillMaxSize()) {
if (showFullScreenNote && fullScreenNote != null) {
EditableFullScreenNoteView(
note = fullScreenNote!!,
onBack = {
showFullScreenNote = false
fullScreenNote = null
},
onSave = { title, content ->
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(
title = title,
content = content,
timestamp = System.currentTimeMillis()
) { padding ->
Box(modifier = Modifier.fillMaxSize()) {
if (showFullScreenNote && fullScreenNote != null) {
EditableFullScreenNoteView(
note = fullScreenNote!!,
onBack = {
showFullScreenNote = false
fullScreenNote = null
},
onSave = { title, content ->
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(
title = title,
content = content,
timestamp = System.currentTimeMillis()
)
else it
}
fullScreenNote = fullScreenNote!!.copy(title = title, content = content)
},
onArchive = {
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(isArchived = true)
else it
}
showFullScreenNote = false
fullScreenNote = null
},
onDelete = {
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(isDeleted = true)
else it
}
showFullScreenNote = false
fullScreenNote = null
},
onPinToggle = {
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(isPinned = !it.isPinned)
else it
}
fullScreenNote = fullScreenNote!!.copy(isPinned = !fullScreenNote!!.isPinned)
}
)
} else {
Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
) {
when (currentScreen) {
"main" -> MainScreen(
categories = categories,
notes = notes,
selectedCategory = selectedCategory,
searchQuery = searchQuery,
onCategoryClick = { selectedCategory = it },
onNoteClick = { note ->
fullScreenNote = note
showFullScreenNote = true
},
onNoteLongClick = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isArchived = true)
else it
}
},
onPinToggle = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isPinned = !it.isPinned)
else it
}
},
onCategoryLongClick = { category ->
categories = categories.filter { it.id != category.id }
notes = notes.filter { it.categoryId != category.id }
}
)
"starred" -> StarredNotesScreen(
notes = notes,
categories = categories,
onNoteClick = { note ->
fullScreenNote = note
showFullScreenNote = true
},
onMenuClick = { drawerState = true },
onBack = { currentScreen = "main" },
onUnpin = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isPinned = false)
else it
}
}
)
"ai" -> AIHelperScreen(
categories = categories,
notes = notes.filter { !it.isDeleted }
)
"archive" -> ArchiveScreen(
notes = notes.filter { it.isArchived && !it.isDeleted },
categories = categories,
onRestore = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isArchived = false)
else it
}
},
onDelete = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isDeleted = true)
else it
}
}
)
"trash" -> TrashScreen(
notes = notes.filter { it.isDeleted },
categories = categories,
onRestore = { note ->
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 }
}
)
else it
}
fullScreenNote = fullScreenNote!!.copy(title = title, content = content)
},
onArchive = {
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(isArchived = true)
else it
}
showFullScreenNote = false
fullScreenNote = null
},
onDelete = {
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(isDeleted = true)
else it
}
showFullScreenNote = false
fullScreenNote = null
},
onPinToggle = {
notes = notes.map {
if (it.id == fullScreenNote!!.id) it.copy(isPinned = !it.isPinned)
else it
}
fullScreenNote = fullScreenNote!!.copy(isPinned = !fullScreenNote!!.isPinned)
}
)
} else {
Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
) {
when (currentScreen) {
"main" -> MainScreen(
categories = categories,
notes = notes,
selectedCategory = selectedCategory,
searchQuery = searchQuery,
onCategoryClick = { selectedCategory = it },
onNoteClick = { note ->
fullScreenNote = note
showFullScreenNote = true
},
onNoteLongClick = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isArchived = true)
else it
}
},
onPinToggle = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isPinned = !it.isPinned)
else it
}
},
onCategoryLongClick = { category ->
categories = categories.filter { it.id != category.id }
notes = notes.filter { it.categoryId != category.id }
}
)
"ai" -> AIHelperScreen(
categories = categories,
notes = notes.filter { !it.isDeleted }
)
"archive" -> ArchiveScreen(
notes = notes.filter { it.isArchived && !it.isDeleted },
categories = categories,
onRestore = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isArchived = false)
else it
}
},
onDelete = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isDeleted = true)
else it
}
}
)
"trash" -> TrashScreen(
notes = notes.filter { it.isDeleted },
categories = categories,
onRestore = { note ->
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 }
}
)
}
}
}
// Drawer with Animation
AnimatedVisibility(
visible = drawerState,
enter = fadeIn() + slideInHorizontally(),
exit = fadeOut() + slideOutHorizontally()
) {
DrawerMenu(
currentScreen = currentScreen,
onDismiss = { drawerState = false },
onItemClick = { screen ->
currentScreen = screen
selectedCategory = null
drawerState = false
showSearch = false
searchQuery = ""
}
)
}
// Dialogs
if (showCategoryDialog) {
CategoryDialog(
onDismiss = { showCategoryDialog = false },
onSave = { name, gradientStart, gradientEnd ->
categories = categories + Category(
name = name,
gradientStart = gradientStart,
gradientEnd = gradientEnd
)
showCategoryDialog = false
}
)
}
if (showNoteDialog && selectedCategory != null) {
NoteDialog(
note = editingNote,
onDismiss = {
showNoteDialog = false
editingNote = null
},
onSave = { title, content ->
if (editingNote != null) {
notes = notes.map {
if (it.id == editingNote!!.id)
it.copy(
title = title,
content = content,
timestamp = System.currentTimeMillis()
)
else it
}
} else {
notes = notes + Note(
categoryId = selectedCategory!!.id,
title = title,
content = content
// Dialogs
if (showCategoryDialog) {
CategoryDialog(
onDismiss = { showCategoryDialog = false },
onSave = { name, gradientStart, gradientEnd ->
categories = categories + Category(
name = name,
gradientStart = gradientStart,
gradientEnd = gradientEnd
)
showCategoryDialog = false
}
showNoteDialog = false
editingNote = null
},
onDelete = if (editingNote != null) {
{
notes = notes.map {
if (it.id == editingNote!!.id) it.copy(isDeleted = true)
else it
)
}
if (showNoteDialog && selectedCategory != null) {
NoteDialog(
note = editingNote,
onDismiss = {
showNoteDialog = false
editingNote = null
},
onSave = { title, content ->
if (editingNote != null) {
notes = notes.map {
if (it.id == editingNote!!.id)
it.copy(
title = title,
content = content,
timestamp = System.currentTimeMillis()
)
else it
}
} else {
notes = notes + Note(
categoryId = selectedCategory!!.id,
title = title,
content = content
)
}
showNoteDialog = false
editingNote = null
}
} else null
)
},
onDelete = if (editingNote != null) {
{
notes = notes.map {
if (it.id == editingNote!!.id) it.copy(isDeleted = true)
else it
}
showNoteDialog = false
editingNote = null
}
} else null
)
}
}
}
// Drawer with Animation - DI LUAR SCAFFOLD agar di atas semua
AnimatedVisibility(
visible = drawerState,
enter = fadeIn() + slideInHorizontally(
initialOffsetX = { -it }
),
exit = fadeOut() + slideOutHorizontally(
targetOffsetX = { -it }
),
modifier = Modifier.zIndex(100f) // Z-index tinggi
) {
DrawerMenu(
currentScreen = currentScreen,
onDismiss = { drawerState = false },
onItemClick = { screen ->
currentScreen = screen
selectedCategory = null
drawerState = false
showSearch = false
searchQuery = ""
}
)
}
}
}
@ -549,7 +577,7 @@ fun EditableFullScreenNoteView(
onPinToggle()
}) {
Icon(
if (note.isPinned) Icons.Default.Star else Icons.Default.Add,
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
contentDescription = "Pin Catatan",
tint = if (note.isPinned) Color(0xFFFBBF24) else MaterialTheme.colorScheme.onSurface
)
@ -950,19 +978,22 @@ fun NoteCard(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
// Judul
Text(
note.title,
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
IconButton(
onClick = onPinClick,
modifier = Modifier.size(24.dp)
) {
Icon(
if (note.isPinned) Icons.Default.Star else Icons.Default.Add,
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
contentDescription = "Pin",
tint = if (note.isPinned) Color(0xFFFBBF24) else Color.Gray,
modifier = Modifier.size(18.dp)
@ -970,18 +1001,37 @@ fun NoteCard(
}
}
// Deskripsi
if (note.content.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Spacer(modifier = Modifier.height(12.dp))
Text(
text = "Deskripsi",
style = MaterialTheme.typography.labelSmall,
color = Color(0xFF94A3B8),
fontWeight = FontWeight.SemiBold
)
Spacer(modifier = Modifier.height(4.dp))
Text(
note.content,
style = MaterialTheme.typography.bodyMedium,
maxLines = 6,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
color = Color(0xFFCBD5E1),
lineHeight = 20.sp
)
}
Spacer(modifier = Modifier.height(12.dp))
// Divider
Divider(
color = Color(0xFF334155),
thickness = 1.dp
)
Spacer(modifier = Modifier.height(8.dp))
// Timestamp
Text(
dateFormat.format(Date(note.timestamp)),
style = MaterialTheme.typography.bodySmall,
@ -1000,20 +1050,32 @@ fun DrawerMenu(
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.6f))
.clickable(onClick = onDismiss)
.statusBarsPadding() // Padding untuk status bar
.background(Color.Black.copy(alpha = 0.5f))
.clickable(
onClick = onDismiss,
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
) {
Card(
modifier = Modifier
.fillMaxHeight()
.width(280.dp)
.clickable(enabled = false) {},
shape = RoundedCornerShape(0.dp),
.width(250.dp)
.align(Alignment.CenterStart)
.clickable(
onClick = {},
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
shape = RoundedCornerShape(topEnd = 0.dp, bottomEnd = 0.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1E293B)
)
),
elevation = CardDefaults.cardElevation(defaultElevation = 16.dp)
) {
Column(modifier = Modifier.fillMaxSize()) {
// Header Drawer dengan tombol close
Box(
modifier = Modifier
.fillMaxWidth()
@ -1022,38 +1084,69 @@ fun DrawerMenu(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
)
)
.padding(32.dp)
.padding(24.dp)
) {
Column {
Icon(
Icons.Default.Create,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
"AI Notes",
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
fontWeight = FontWeight.Bold
)
Text(
"Smart & Modern",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(0.8f)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Icon(
Icons.Default.Create,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(36.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
"AI Notes",
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
fontWeight = FontWeight.Bold
)
Text(
"Smart & Modern",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(0.8f)
)
}
// // Tombol Close
// IconButton(
// onClick = onDismiss,
// modifier = Modifier
// .size(40.dp)
// .background(
// Color.White.copy(alpha = 0.2f),
// shape = CircleShape
// )
// ) {
// Icon(
// Icons.Default.Close,
// contentDescription = "Tutup Menu",
// tint = Color.White,
// modifier = Modifier.size(24.dp)
// )
// }
}
}
Spacer(modifier = Modifier.height(16.dp))
// Menu Items
MenuItem(
icon = Icons.Default.Home,
text = "Beranda",
isSelected = currentScreen == "main"
) { onItemClick("main") }
MenuItem(
icon = Icons.Default.Star,
text = "Berbintang",
isSelected = currentScreen == "starred"
) { onItemClick("starred") }
MenuItem(
icon = Icons.Default.Archive,
text = "Arsip",
@ -1065,6 +1158,21 @@ fun DrawerMenu(
text = "Sampah",
isSelected = currentScreen == "trash"
) { onItemClick("trash") }
Spacer(modifier = Modifier.weight(1f))
// Footer
Divider(
color = Color.White.copy(alpha = 0.1f),
modifier = Modifier.padding(horizontal = 16.dp)
)
Text(
text = "Version 1.0.0",
style = MaterialTheme.typography.bodySmall,
color = Color.White.copy(alpha = 0.5f),
modifier = Modifier.padding(16.dp)
)
}
}
}
@ -1795,10 +1903,11 @@ fun ChatBubble(
horizontalArrangement = if (message.isUser) Arrangement.End else Arrangement.Start
) {
if (!message.isUser) {
// Ganti ikon bintang dengan ikon robot/sparkles
Icon(
Icons.Default.Star,
Icons.Default.AutoAwesome, // Atau bisa diganti dengan ikon lain seperti AutoAwesome
contentDescription = null,
tint = Color(0xFFFBBF24),
tint = Color(0xFF6366F1), // Warna ungu/biru untuk AI
modifier = Modifier
.size(32.dp)
.padding(end = 8.dp)
@ -2168,6 +2277,136 @@ fun TrashNoteCard(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StarredNotesScreen(
notes: List<Note>,
categories: List<Category>,
onNoteClick: (Note) -> Unit,
onMenuClick: () -> Unit,
onBack: () -> Unit,
onUnpin: (Note) -> Unit
) {
val starredNotes = notes.filter { it.isPinned && !it.isArchived && !it.isDeleted }
if (starredNotes.isEmpty()) {
EmptyState(
icon = Icons.Default.Star,
message = "Belum ada catatan berbintang",
subtitle = "Catatan yang ditandai berbintang akan muncul di sini"
)
} else {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(starredNotes) { note ->
val category = categories.find { it.id == note.categoryId }
StarredNoteCard(
note = note,
categoryName = category?.name ?: "Unknown",
onClick = { onNoteClick(note) },
onUnpin = { onUnpin(note) }
)
}
}
}
}
@Composable
fun StarredNoteCard(
note: Note,
categoryName: String,
onClick: () -> Unit,
onUnpin: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1E293B)
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
Column(modifier = Modifier.weight(1f)) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Star,
contentDescription = null,
tint = Color(0xFFFBBF24),
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
note.title,
fontWeight = FontWeight.Bold,
color = Color.White,
style = MaterialTheme.typography.titleMedium
)
}
Spacer(modifier = Modifier.height(4.dp))
Text(
categoryName,
color = Color(0xFF64748B),
style = MaterialTheme.typography.bodySmall
)
}
}
if (note.content.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Text(
note.content,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = Color(0xFF94A3B8),
style = MaterialTheme.typography.bodyMedium
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onClick) {
Icon(
Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(18.dp),
tint = Color(0xFF6366F1)
)
Spacer(modifier = Modifier.width(4.dp))
Text("Lihat Detail", color = Color(0xFF6366F1), fontWeight = FontWeight.Bold)
}
Spacer(modifier = Modifier.width(8.dp))
TextButton(onClick = onUnpin) {
Icon(
Icons.Outlined.StarBorder,
contentDescription = null,
modifier = Modifier.size(18.dp),
tint = Color(0xFFFBBF24)
)
Spacer(modifier = Modifier.width(4.dp))
Text("Hapus Bintang", color = Color(0xFFFBBF24), fontWeight = FontWeight.Bold)
}
}
}
}
}
@Composable
fun EmptyState(
icon: ImageVector,