package com.example.notesai import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.* import androidx.compose.animation.core.* 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 import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import java.util.UUID import androidx.compose.material.icons.outlined.Star import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.HorizontalDivider as Divider import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Send import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import com.google.ai.client.generativeai.GenerativeModel 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.platform.LocalContext 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 data class Category( val id: String = UUID.randomUUID().toString(), val name: String, val gradientStart: Long, val gradientEnd: Long, val timestamp: Long = System.currentTimeMillis() ) data class Note( val id: String = UUID.randomUUID().toString(), val categoryId: String, val title: String, val content: String, val timestamp: Long = System.currentTimeMillis(), val isArchived: Boolean = false, val isDeleted: Boolean = false, val isPinned: Boolean = false ) data class ChatMessage( val id: String = UUID.randomUUID().toString(), val message: String, val isUser: Boolean, val timestamp: Long = System.currentTimeMillis() ) class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme( colorScheme = darkColorScheme( primary = Color(0xFF6366F1), secondary = Color(0xFFA855F7), background = Color(0xFF0F172A), surface = Color(0xFF1E293B), onPrimary = Color.White, onSecondary = Color.White, onBackground = Color(0xFFE2E8F0), onSurface = Color(0xFFE2E8F0) ) ) { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { NotesApp() } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun NotesApp() { val context = LocalContext.current val dataStoreManager = remember { DataStoreManager(context) } val scope = rememberCoroutineScope() var categories by remember { mutableStateOf(listOf()) } var notes by remember { mutableStateOf(listOf()) } var selectedCategory by remember { mutableStateOf(null) } var currentScreen by remember { mutableStateOf("main") } var drawerState by remember { mutableStateOf(false) } var showCategoryDialog by remember { mutableStateOf(false) } var showNoteDialog by remember { mutableStateOf(false) } var editingNote by remember { mutableStateOf(null) } var searchQuery by remember { mutableStateOf("") } var showSearch by remember { mutableStateOf(false) } var showFullScreenNote by remember { mutableStateOf(false) } var fullScreenNote by remember { mutableStateOf(null) } // Load data dari DataStore LaunchedEffect(Unit) { try { dataStoreManager.categoriesFlow.collect { loadedCategories -> categories = loadedCategories } } catch (e: Exception) { e.printStackTrace() } } LaunchedEffect(Unit) { try { dataStoreManager.notesFlow.collect { loadedNotes -> notes = loadedNotes } } catch (e: Exception) { e.printStackTrace() } } // Simpan categories dengan debounce LaunchedEffect(categories.size) { if (categories.isNotEmpty()) { delay(500) try { dataStoreManager.saveCategories(categories) } catch (e: Exception) { e.printStackTrace() } } } // Simpan notes dengan debounce LaunchedEffect(notes.size) { if (notes.isNotEmpty()) { delay(500) try { dataStoreManager.saveNotes(notes) } catch (e: Exception) { e.printStackTrace() } } } 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() ) { 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" } ) } } ) { 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 }, onPinToggle = { note -> notes = notes.map { if (it.id == note.id) it.copy(isPinned = !it.isPinned) else it } }, onCategoryDelete = { category -> // Delete kategori dan semua catatan di dalamnya categories = categories.filter { it.id != category.id } notes = notes.filter { it.categoryId != category.id } selectedCategory = null } ) "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 } } ) } } } // 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 ) } showNoteDialog = false editingNote = 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 = "" } ) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditableFullScreenNoteView( note: Note, onBack: () -> Unit, onSave: (String, String) -> Unit, onArchive: () -> Unit, onDelete: () -> Unit, onPinToggle: () -> Unit ) { var title by remember { mutableStateOf(note.title) } var content by remember { mutableStateOf(note.content) } var showArchiveDialog by remember { mutableStateOf(false) } var showDeleteDialog by remember { mutableStateOf(false) } val dateFormat = remember { SimpleDateFormat("dd MMMM yyyy, HH:mm", Locale("id", "ID")) } // Dialog Konfirmasi Arsip if (showArchiveDialog) { AlertDialog( onDismissRequest = { showArchiveDialog = false }, title = { Text( text = "Arsipkan Catatan?", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) }, text = { Text( text = "Catatan ini akan dipindahkan ke arsip. Anda masih bisa mengaksesnya nanti.", style = MaterialTheme.typography.bodyMedium ) }, confirmButton = { TextButton( onClick = { if (title.isNotBlank()) { onSave(title, content) } onArchive() showArchiveDialog = false } ) { Text("Arsipkan", color = MaterialTheme.colorScheme.primary) } }, dismissButton = { TextButton(onClick = { showArchiveDialog = false }) { Text("Batal", color = MaterialTheme.colorScheme.onSurface) } } ) } // Dialog Konfirmasi Hapus if (showDeleteDialog) { AlertDialog( onDismissRequest = { showDeleteDialog = false }, title = { Text( text = "Hapus Catatan?", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) }, text = { Text( text = "Catatan ini akan dihapus permanen dan tidak bisa dikembalikan.", style = MaterialTheme.typography.bodyMedium ) }, confirmButton = { TextButton( onClick = { onDelete() showDeleteDialog = false } ) { Text("Hapus", color = Color(0xFFEF4444)) } }, dismissButton = { TextButton(onClick = { showDeleteDialog = false }) { Text("Batal", color = MaterialTheme.colorScheme.onSurface) } } ) } Scaffold( containerColor = MaterialTheme.colorScheme.background, topBar = { TopAppBar( title = { }, navigationIcon = { IconButton(onClick = { if (title.isNotBlank()) { onSave(title, content) } onBack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Kembali", tint = MaterialTheme.colorScheme.onBackground) } }, actions = { IconButton(onClick = { if (title.isNotBlank()) { onSave(title, content) } onPinToggle() }) { Icon( if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder, contentDescription = "Pin Catatan", tint = if (note.isPinned) Color(0xFFFBBF24) else MaterialTheme.colorScheme.onSurface ) } IconButton(onClick = { showArchiveDialog = true }) { Icon(Icons.Default.Archive, contentDescription = "Arsipkan", tint = MaterialTheme.colorScheme.onSurface) } IconButton(onClick = { showDeleteDialog = true }) { Icon(Icons.Default.Delete, contentDescription = "Hapus", tint = MaterialTheme.colorScheme.onSurface) } }, colors = TopAppBarDefaults.topAppBarColors( containerColor = Color.Transparent ) ) } ) { padding -> Column( modifier = Modifier .fillMaxSize() .padding(padding) .verticalScroll(rememberScrollState()) .padding(horizontal = 20.dp) ) { TextField( value = title, onValueChange = { title = it }, textStyle = MaterialTheme.typography.headlineLarge.copy( fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onBackground ), placeholder = { Text( "Judul", style = MaterialTheme.typography.headlineLarge, color = Color(0xFF64748B) ) }, colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, cursorColor = Color(0xFFA855F7) ), modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(12.dp)) Text( text = "Terakhir diubah: ${dateFormat.format(Date(note.timestamp))}", style = MaterialTheme.typography.bodySmall, color = Color(0xFF64748B) ) Divider( modifier = Modifier.padding(vertical = 20.dp), color = MaterialTheme.colorScheme.surface ) TextField( value = content, onValueChange = { content = it }, textStyle = MaterialTheme.typography.bodyLarge.copy( color = MaterialTheme.colorScheme.onSurface, lineHeight = 28.sp ), placeholder = { Text( "Mulai menulis...", style = MaterialTheme.typography.bodyLarge, color = Color(0xFF64748B) ) }, colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, cursorColor = Color(0xFFA855F7) ), modifier = Modifier .fillMaxWidth() .heightIn(min = 400.dp) ) Spacer(modifier = Modifier.height(100.dp)) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun ModernTopBar( title: String, showBackButton: Boolean, onBackClick: () -> Unit, onMenuClick: () -> Unit, onSearchClick: () -> Unit, searchQuery: String, onSearchQueryChange: (String) -> Unit, showSearch: Boolean ) { TopAppBar( title = { if (showSearch) { TextField( value = searchQuery, onValueChange = onSearchQueryChange, placeholder = { Text("Cari catatan...", color = Color.White.copy(0.6f)) }, colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, focusedTextColor = Color.White, unfocusedTextColor = Color.White, cursorColor = Color.White, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent ), modifier = Modifier.fillMaxWidth() ) } else { Text( title, fontWeight = FontWeight.Bold, fontSize = 22.sp ) } }, navigationIcon = { IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) { Icon( if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu, contentDescription = null, tint = Color.White ) } }, actions = { IconButton(onClick = onSearchClick) { Icon( if (showSearch) Icons.Default.Close else Icons.Default.Search, contentDescription = "Search", tint = Color.White ) } }, colors = TopAppBarDefaults.topAppBarColors( containerColor = Color.Transparent ), modifier = Modifier .background( brush = Brush.horizontalGradient( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) ) ) ) } @Composable fun ModernBottomBar( currentScreen: String, onHomeClick: () -> Unit, onAIClick: () -> Unit ) { BottomAppBar( containerColor = Color.Transparent, modifier = Modifier .shadow(8.dp, RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) .background( brush = Brush.verticalGradient( colors = listOf( Color(0xFF1E293B).copy(0.95f), Color(0xFF334155).copy(0.95f) ) ), shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp) ) ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .weight(1f) .clickable(onClick = onHomeClick) .padding(vertical = 8.dp) ) { Icon( Icons.Default.Home, contentDescription = "Home", tint = if (currentScreen == "main") Color(0xFF6366F1) else Color(0xFF94A3B8), modifier = Modifier.size(24.dp) ) Spacer(modifier = Modifier.height(4.dp)) Text( "Home", color = if (currentScreen == "main") Color(0xFF6366F1) else Color(0xFF94A3B8), style = MaterialTheme.typography.bodySmall, fontWeight = if (currentScreen == "main") FontWeight.Bold else FontWeight.Normal ) } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .weight(1f) .clickable(onClick = onAIClick) .padding(vertical = 8.dp) ) { Icon( Icons.Default.Star, contentDescription = "AI Helper", tint = if (currentScreen == "ai") Color(0xFFFBBF24) else Color(0xFF94A3B8), modifier = Modifier.size(24.dp) ) Spacer(modifier = Modifier.height(4.dp)) Text( "AI Helper", color = if (currentScreen == "ai") Color(0xFFFBBF24) else Color(0xFF94A3B8), style = MaterialTheme.typography.bodySmall, fontWeight = if (currentScreen == "ai") FontWeight.Bold else FontWeight.Normal ) } } } } @OptIn(ExperimentalFoundationApi::class) @Composable fun MainScreen( categories: List, notes: List, selectedCategory: Category?, searchQuery: String, onCategoryClick: (Category) -> Unit, onNoteClick: (Note) -> Unit, onPinToggle: (Note) -> Unit, onCategoryDelete: (Category) -> Unit ) { Column(modifier = Modifier.fillMaxSize()) { if (selectedCategory == null) { // Beranda: Tampilkan kategori dengan search filtering if (categories.isEmpty()) { EmptyState( icon = Icons.Default.Create, message = "Buat kategori pertama Anda", subtitle = "Tekan tombol + untuk memulai" ) } else { // Filter kategori berdasarkan searchQuery val filteredCategories = if (searchQuery.isEmpty()) { categories } else { categories.filter { it.name.contains(searchQuery, ignoreCase = true) } } if (filteredCategories.isEmpty()) { EmptyState( icon = Icons.Default.Search, message = "Kategori tidak ditemukan", subtitle = "Coba kata kunci lain" ) } else { LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Fixed(2), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalItemSpacing = 12.dp, modifier = Modifier.fillMaxSize() ) { items(filteredCategories) { category -> CategoryCard( category = category, noteCount = notes.count { it.categoryId == category.id && !it.isDeleted && !it.isArchived }, onClick = { onCategoryClick(category) }, onDelete = { onCategoryDelete(category) } ) } } } } } else { val categoryNotes = notes .filter { it.categoryId == selectedCategory.id && !it.isDeleted && !it.isArchived && (searchQuery.isEmpty() || it.title.contains(searchQuery, ignoreCase = true) || it.content.contains(searchQuery, ignoreCase = true)) } .sortedWith(compareByDescending { it.isPinned }.thenByDescending { it.timestamp }) if (categoryNotes.isEmpty()) { EmptyState( icon = Icons.Default.Add, message = if (searchQuery.isEmpty()) "Belum ada catatan" else "Tidak ada hasil", subtitle = if (searchQuery.isEmpty()) "Tekan tombol + untuk membuat catatan" else "Coba kata kunci lain" ) } else { LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Fixed(2), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalItemSpacing = 12.dp, modifier = Modifier.fillMaxSize() ) { items(categoryNotes) { note -> NoteCard( note = note, onClick = { onNoteClick(note) }, onPinClick = { onPinToggle(note) }, ) } } } } } } @OptIn(ExperimentalFoundationApi::class) @Composable fun CategoryCard( category: Category, noteCount: Int, onClick: () -> Unit, onDelete: () -> Unit = {} ) { var showDeleteConfirm by remember { mutableStateOf(false) } // Delete confirmation dialog if (showDeleteConfirm) { AlertDialog( onDismissRequest = { showDeleteConfirm = false }, title = { Text("Hapus Kategori?", color = Color.White) }, text = { Text("Kategori '${category.name}' dan semua catatan di dalamnya akan dihapus. Tindakan ini tidak dapat dibatalkan.", color = Color.White) }, confirmButton = { Button( onClick = { onDelete() showDeleteConfirm = false }, colors = ButtonDefaults.buttonColors( containerColor = Color(0xFFEF4444) ) ) { Text("Hapus", color = Color.White) } }, dismissButton = { Button( onClick = { showDeleteConfirm = false }, colors = ButtonDefaults.buttonColors( containerColor = Color(0xFF64748B) ) ) { Text("Batal", color = Color.White) } }, containerColor = Color(0xFF1E293B) ) } Card( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.Transparent), elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) ) { Box( modifier = Modifier .fillMaxWidth() .background( brush = Brush.linearGradient( colors = listOf( Color(category.gradientStart), Color(category.gradientEnd) ) ) ) .padding(20.dp) ) { Column { Icon( Icons.Default.Folder, contentDescription = null, tint = Color.White.copy(0.9f), modifier = Modifier.size(32.dp) ) Spacer(modifier = Modifier.height(12.dp)) Text( category.name, style = MaterialTheme.typography.titleMedium, 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) ) } // Delete button di top-right corner IconButton( onClick = { showDeleteConfirm = true }, modifier = Modifier .align(Alignment.TopEnd) .size(40.dp) ) { Icon( Icons.Default.Close, contentDescription = "Hapus kategori", tint = Color.White.copy(0.7f), modifier = Modifier.size(20.dp) ) } } } } @OptIn(ExperimentalFoundationApi::class) @Composable fun NoteCard( note: Note, onClick: () -> Unit, onPinClick: () -> Unit ) { val dateFormat = SimpleDateFormat("dd MMM, HH:mm", Locale("id", "ID")) Card( modifier = Modifier .fillMaxWidth() .combinedClickable( onClick = onClick, ), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( containerColor = Color(0xFF1E293B) ), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top ) { // Judul Text( note.title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.weight(1f), maxLines = 2, overflow = TextOverflow.Ellipsis ) IconButton( onClick = onPinClick, modifier = Modifier.size(24.dp) ) { Icon( 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) ) } } // Deskripsi if (note.content.isNotEmpty()) { 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 = 4, overflow = TextOverflow.Ellipsis, color = Color(0xFFCBD5E1), lineHeight = 20.sp ) } Spacer(modifier = Modifier.height(12.dp)) Divider( color = Color(0xFF334155), thickness = 1.dp ) Spacer(modifier = Modifier.height(8.dp)) // Timestamp Text( dateFormat.format(Date(note.timestamp)), style = MaterialTheme.typography.bodySmall, color = Color(0xFF64748B) ) } } } @Composable fun DrawerMenu( currentScreen: String, onDismiss: () -> Unit, onItemClick: (String) -> Unit ) { Box( modifier = Modifier .fillMaxSize() .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(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() .background( brush = Brush.verticalGradient( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) ) ) .padding(24.dp) ) { 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", isSelected = currentScreen == "archive" ) { onItemClick("archive") } MenuItem( icon = Icons.Default.Delete, 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) ) } } } } @Composable fun MenuItem( icon: ImageVector, text: String, isSelected: Boolean, onClick: () -> Unit ) { Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick) .background( if (isSelected) Color(0xFF334155) else Color.Transparent ) .padding(horizontal = 20.dp, vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( icon, contentDescription = null, tint = if (isSelected) Color(0xFFA855F7) else Color(0xFF94A3B8) ) Spacer(modifier = Modifier.width(16.dp)) Text( text, style = MaterialTheme.typography.bodyLarge, color = if (isSelected) Color.White else Color(0xFF94A3B8), fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal ) } } @Composable fun CategoryDialog( onDismiss: () -> Unit, onSave: (String, Long, Long) -> Unit ) { var name by remember { mutableStateOf("") } var selectedGradient by remember { mutableStateOf(0) } val gradients = listOf( Pair(0xFF6366F1L, 0xFFA855F7L), Pair(0xFFEC4899L, 0xFFF59E0BL), Pair(0xFF8B5CF6L, 0xFFEC4899L), Pair(0xFF06B6D4L, 0xFF3B82F6L), Pair(0xFF10B981L, 0xFF059669L), Pair(0xFFF59E0BL, 0xFFEF4444L), Pair(0xFF6366F1L, 0xFF8B5CF6L), Pair(0xFFEF4444L, 0xFFDC2626L) ) AlertDialog( onDismissRequest = onDismiss, containerColor = Color(0xFF1E293B), title = { Text( "Buat Kategori Baru", color = Color.White, fontWeight = FontWeight.Bold ) }, text = { Column { OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Nama Kategori", color = Color(0xFF94A3B8)) }, modifier = Modifier.fillMaxWidth(), colors = TextFieldDefaults.colors( focusedTextColor = Color.White, unfocusedTextColor = Color.White, focusedContainerColor = Color(0xFF334155), unfocusedContainerColor = Color(0xFF334155), cursorColor = Color(0xFFA855F7), focusedIndicatorColor = Color(0xFFA855F7), unfocusedIndicatorColor = Color(0xFF64748B) ), shape = RoundedCornerShape(12.dp) ) Spacer(modifier = Modifier.height(20.dp)) Text( "Pilih Gradient:", style = MaterialTheme.typography.bodyMedium, color = Color.White, fontWeight = FontWeight.SemiBold ) Spacer(modifier = Modifier.height(12.dp)) gradients.chunked(4).forEach { row -> Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { row.forEachIndexed { index, gradient -> val globalIndex = gradients.indexOf(gradient) Box( modifier = Modifier .weight(1f) .aspectRatio(1f) .clip(RoundedCornerShape(12.dp)) .background( brush = Brush.linearGradient( colors = listOf( Color(gradient.first), Color(gradient.second) ) ) ) .clickable { selectedGradient = globalIndex }, contentAlignment = Alignment.Center ) { if (selectedGradient == globalIndex) { Icon( Icons.Default.Check, contentDescription = null, tint = Color.White, modifier = Modifier.size(24.dp) ) } } } } Spacer(modifier = Modifier.height(8.dp)) } } }, confirmButton = { Button( onClick = { if (name.isNotBlank()) { val gradient = gradients[selectedGradient] onSave(name, gradient.first, gradient.second) } }, enabled = name.isNotBlank(), colors = ButtonDefaults.buttonColors( containerColor = Color.Transparent ), modifier = Modifier.background( brush = Brush.linearGradient( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) ), shape = RoundedCornerShape(8.dp) ) ) { Text("Simpan", color = Color.White, fontWeight = FontWeight.Bold) } }, dismissButton = { TextButton(onClick = onDismiss) { Text("Batal", color = Color(0xFF94A3B8)) } } ) } @Composable fun NoteDialog( note: Note?, onDismiss: () -> Unit, onSave: (String, String) -> Unit, onDelete: (() -> Unit)? ) { var title by remember { mutableStateOf(note?.title ?: "") } var content by remember { mutableStateOf(note?.content ?: "") } AlertDialog( onDismissRequest = onDismiss, containerColor = Color(0xFF1E293B), title = { Text( if (note == null) "Catatan Baru" else "Edit Catatan", color = Color.White, fontWeight = FontWeight.Bold ) }, text = { Column { OutlinedTextField( value = title, onValueChange = { title = it }, label = { Text("Judul", color = Color(0xFF94A3B8)) }, modifier = Modifier.fillMaxWidth(), colors = TextFieldDefaults.colors( focusedTextColor = Color.White, unfocusedTextColor = Color.White, focusedContainerColor = Color(0xFF334155), unfocusedContainerColor = Color(0xFF334155), cursorColor = Color(0xFFA855F7), focusedIndicatorColor = Color(0xFFA855F7), unfocusedIndicatorColor = Color(0xFF64748B) ), shape = RoundedCornerShape(12.dp) ) Spacer(modifier = Modifier.height(12.dp)) OutlinedTextField( value = content, onValueChange = { content = it }, label = { Text("Isi Catatan", color = Color(0xFF94A3B8)) }, modifier = Modifier .fillMaxWidth() .height(200.dp), maxLines = 10, colors = TextFieldDefaults.colors( focusedTextColor = Color.White, unfocusedTextColor = Color.White, focusedContainerColor = Color(0xFF334155), unfocusedContainerColor = Color(0xFF334155), cursorColor = Color(0xFFA855F7), focusedIndicatorColor = Color(0xFFA855F7), unfocusedIndicatorColor = Color(0xFF64748B) ), shape = RoundedCornerShape(12.dp) ) } }, confirmButton = { Row { if (onDelete != null) { TextButton(onClick = onDelete) { Text("Hapus", color = Color(0xFFEF4444), fontWeight = FontWeight.Bold) } Spacer(modifier = Modifier.width(8.dp)) } Button( onClick = { if (title.isNotBlank()) onSave(title, content) }, enabled = title.isNotBlank(), colors = ButtonDefaults.buttonColors( containerColor = Color.Transparent ), modifier = Modifier.background( brush = Brush.linearGradient( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) ), shape = RoundedCornerShape(8.dp) ) ) { Text("Simpan", color = Color.White, fontWeight = FontWeight.Bold) } } }, dismissButton = { TextButton(onClick = onDismiss) { Text("Batal", color = Color(0xFF94A3B8)) } } ) } @OptIn(ExperimentalMaterial3Api::class) @Composable fun AIHelperScreen( categories: List, notes: List ) { var prompt by remember { mutableStateOf("") } var isLoading by remember { mutableStateOf(false) } var selectedCategory by remember { mutableStateOf(null) } var showCategoryDropdown by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf("") } var chatMessages by remember { mutableStateOf(listOf()) } var showCopiedMessage by remember { mutableStateOf(false) } var copiedMessageId by remember { mutableStateOf("") } val scope = rememberCoroutineScope() val clipboardManager = LocalClipboardManager.current val scrollState = rememberScrollState() // Inisialisasi Gemini Model val generativeModel = remember { GenerativeModel( modelName = "gemini-2.5-flash", apiKey = APIKey.GEMINI_API_KEY, generationConfig = generationConfig { temperature = 0.8f topK = 40 topP = 0.95f maxOutputTokens = 4096 candidateCount = 1 } ) } // Auto scroll ke bawah saat ada pesan baru LaunchedEffect(chatMessages.size) { if (chatMessages.isNotEmpty()) { delay(100) scrollState.animateScrollTo(scrollState.maxValue) } } Column( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) ) { // Header Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.Transparent), shape = RoundedCornerShape(0.dp) ) { Box( modifier = Modifier .fillMaxWidth() .background( brush = Brush.linearGradient( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) ) ) .padding(20.dp) ) { Column { Row(verticalAlignment = Alignment.CenterVertically) { Icon( Icons.Default.Star, contentDescription = null, tint = Color(0xFFFBBF24), modifier = Modifier.size(28.dp) ) Spacer(modifier = Modifier.width(12.dp)) Column { Text( "AI Helper", style = MaterialTheme.typography.titleLarge, color = Color.White, fontWeight = FontWeight.Bold ) Text( "Powered by Gemini AI", style = MaterialTheme.typography.bodySmall, color = Color.White.copy(0.8f) ) } } } } } // Category Selector & Stats - Compact Version Column( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.background) .padding(16.dp) ) { // Category Selector Box { Card( modifier = Modifier .fillMaxWidth() .clickable { showCategoryDropdown = !showCategoryDropdown }, colors = CardDefaults.cardColors(containerColor = Color(0xFF1E293B)), shape = RoundedCornerShape(12.dp) ) { Row( modifier = Modifier .fillMaxWidth() .padding(12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( Icons.Default.Folder, contentDescription = null, tint = Color(0xFF6366F1), modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(8.dp)) Text( selectedCategory?.name ?: "Semua Kategori", color = Color.White, style = MaterialTheme.typography.bodyMedium ) } Icon( Icons.Default.ArrowDropDown, contentDescription = null, tint = Color(0xFF94A3B8) ) } } DropdownMenu( expanded = showCategoryDropdown, onDismissRequest = { showCategoryDropdown = false }, modifier = Modifier .fillMaxWidth() .background(Color(0xFF1E293B)) ) { DropdownMenuItem( text = { Text("Semua Kategori", color = Color.White) }, onClick = { selectedCategory = null showCategoryDropdown = false } ) categories.forEach { category -> DropdownMenuItem( text = { Text(category.name, color = Color.White) }, onClick = { selectedCategory = category showCategoryDropdown = false } ) } } } // Stats - Compact Spacer(modifier = Modifier.height(12.dp)) val filteredNotes = if (selectedCategory != null) { notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived } } else { notes.filter { !it.isArchived } } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { CompactStatItem( label = "Total", value = filteredNotes.size.toString(), color = Color(0xFF6366F1) ) CompactStatItem( label = "Dipasang", value = filteredNotes.count { it.isPinned }.toString(), color = Color(0xFFFBBF24) ) CompactStatItem( label = "Kategori", value = categories.size.toString(), color = Color(0xFFA855F7) ) } } Divider(color = Color(0xFF334155), thickness = 1.dp) // Chat Area Column( modifier = Modifier .weight(1f) .fillMaxWidth() ) { if (chatMessages.isEmpty()) { // Welcome State Column( modifier = Modifier .fillMaxSize() .padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Icon( Icons.Default.Star, contentDescription = null, modifier = Modifier.size(64.dp), tint = Color(0xFF6366F1).copy(0.5f) ) Spacer(modifier = Modifier.height(16.dp)) Text( "Mulai Percakapan", style = MaterialTheme.typography.titleLarge, color = Color.White, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) Text( "Tanyakan apa saja tentang catatan Anda", style = MaterialTheme.typography.bodyMedium, color = Color(0xFF94A3B8), textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(24.dp)) // Suggestion Chips Column( horizontalAlignment = Alignment.Start, modifier = Modifier.fillMaxWidth(0.8f) ) { Text( "Contoh pertanyaan:", style = MaterialTheme.typography.bodySmall, color = Color(0xFF64748B), modifier = Modifier.padding(bottom = 8.dp) ) SuggestionChip("Analisis catatan saya", onSelect = { prompt = it }) SuggestionChip("Buat ringkasan", onSelect = { prompt = it }) SuggestionChip("Berikan saran organisasi", onSelect = { prompt = it }) } } } else { // Chat Messages Column( modifier = Modifier .fillMaxSize() .verticalScroll(scrollState) .padding(16.dp) ) { chatMessages.forEach { message -> ChatBubble( message = message, onCopy = { clipboardManager.setText(AnnotatedString(message.message)) copiedMessageId = message.id showCopiedMessage = true scope.launch { delay(2000) showCopiedMessage = false } }, showCopied = showCopiedMessage && copiedMessageId == message.id ) Spacer(modifier = Modifier.height(12.dp)) } // Loading Indicator if (isLoading) { Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), horizontalArrangement = Arrangement.Start ) { Card( colors = CardDefaults.cardColors( containerColor = Color(0xFF1E293B) ), shape = RoundedCornerShape(16.dp) ) { Row( modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { CircularProgressIndicator( modifier = Modifier.size(20.dp), color = Color(0xFF6366F1), strokeWidth = 2.dp ) Spacer(modifier = Modifier.width(12.dp)) Text( "AI sedang berpikir...", color = Color(0xFF94A3B8), style = MaterialTheme.typography.bodyMedium ) } } } } // Error Message if (errorMessage.isNotEmpty()) { Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( containerColor = Color(0xFFEF4444).copy(0.2f) ), shape = RoundedCornerShape(12.dp) ) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( Icons.Default.Warning, contentDescription = null, tint = Color(0xFFEF4444), modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(8.dp)) Text( errorMessage, color = Color(0xFFEF4444), style = MaterialTheme.typography.bodySmall ) } } } Spacer(modifier = Modifier.height(80.dp)) } } } // Input Area Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( containerColor = Color(0xFF1E293B) ), shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp), elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) ) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.Bottom ) { OutlinedTextField( value = prompt, onValueChange = { prompt = it }, placeholder = { Text( "Ketik pesan...", color = Color(0xFF64748B) ) }, modifier = Modifier .weight(1f) .heightIn(min = 48.dp, max = 120.dp), colors = TextFieldDefaults.colors( focusedTextColor = Color.White, unfocusedTextColor = Color.White, focusedContainerColor = Color(0xFF334155), unfocusedContainerColor = Color(0xFF334155), cursorColor = Color(0xFFA855F7), focusedIndicatorColor = Color(0xFF6366F1), unfocusedIndicatorColor = Color(0xFF475569) ), shape = RoundedCornerShape(24.dp), maxLines = 4 ) Spacer(modifier = Modifier.width(12.dp)) // Send Button FloatingActionButton( onClick = { if (prompt.isNotBlank() && !isLoading) { scope.launch { // Add user message chatMessages = chatMessages + ChatMessage( message = prompt, isUser = true ) val userPrompt = prompt prompt = "" isLoading = true errorMessage = "" try { val filteredNotes = if (selectedCategory != null) { notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived } } else { notes.filter { !it.isArchived } } val notesContext = buildString { appendLine("Data catatan pengguna:") appendLine("Total catatan: ${filteredNotes.size}") appendLine("Kategori: ${selectedCategory?.name ?: "Semua"}") appendLine() appendLine("Daftar catatan:") filteredNotes.take(10).forEach { note -> appendLine("- Judul: ${note.title}") appendLine(" Isi: ${note.content.take(100)}") appendLine() } } val fullPrompt = "$notesContext\n\nPertanyaan: $userPrompt\n\nBerikan jawaban dalam bahasa Indonesia yang natural dan membantu." val result = generativeModel.generateContent(fullPrompt) val response = result.text ?: "Tidak ada respons dari AI" // Add AI response chatMessages = chatMessages + ChatMessage( message = response, isUser = false ) } catch (e: Exception) { errorMessage = "Error: ${e.message}" } finally { isLoading = false } } } }, containerColor = Color.Transparent, modifier = Modifier .size(48.dp) .background( brush = Brush.linearGradient( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) ), shape = CircleShape ) ) { Icon( Icons.Default.Send, contentDescription = "Send", tint = Color.White, modifier = Modifier.size(24.dp) ) } } } } } @Composable fun ChatBubble( message: ChatMessage, onCopy: () -> Unit, showCopied: Boolean ) { val dateFormat = remember { SimpleDateFormat("HH:mm", Locale("id", "ID")) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = if (message.isUser) Arrangement.End else Arrangement.Start ) { if (!message.isUser) { // Ganti ikon bintang dengan ikon robot/sparkles Icon( Icons.Default.AutoAwesome, // Atau bisa diganti dengan ikon lain seperti AutoAwesome contentDescription = null, tint = Color(0xFF6366F1), // Warna ungu/biru untuk AI modifier = Modifier .size(32.dp) .padding(end = 8.dp) ) } Column( modifier = Modifier.fillMaxWidth(0.85f), horizontalAlignment = if (message.isUser) Alignment.End else Alignment.Start ) { Card( colors = CardDefaults.cardColors( containerColor = if (message.isUser) Color(0xFF6366F1) else Color(0xFF1E293B) ), shape = RoundedCornerShape( topStart = 16.dp, topEnd = 16.dp, bottomStart = if (message.isUser) 16.dp else 4.dp, bottomEnd = if (message.isUser) 4.dp else 16.dp ) ) { Column(modifier = Modifier.padding(12.dp)) { Text( message.message, color = Color.White, style = MaterialTheme.typography.bodyMedium, lineHeight = 20.sp ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( dateFormat.format(Date(message.timestamp)), color = Color.White.copy(0.6f), style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 4.dp) ) if (!message.isUser) { IconButton( onClick = onCopy, modifier = Modifier.size(32.dp) ) { Icon( Icons.Default.ContentCopy, contentDescription = "Copy", tint = Color.White.copy(0.7f), modifier = Modifier.size(16.dp) ) } } } } } if (showCopied && !message.isUser) { Text( "✓ Disalin", color = Color(0xFF10B981), style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 4.dp, start = 8.dp) ) } } } } @Composable fun CompactStatItem(label: String, value: String, color: Color) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .background( color = Color(0xFF1E293B), shape = RoundedCornerShape(8.dp) ) .padding(horizontal = 12.dp, vertical = 8.dp) ) { Text( value, style = MaterialTheme.typography.titleMedium, color = color, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.width(4.dp)) Text( label, style = MaterialTheme.typography.bodySmall, color = Color(0xFF94A3B8) ) } } @Composable fun SuggestionChip(text: String, onSelect: (String) -> Unit) { Card( modifier = Modifier .padding(vertical = 4.dp) .clickable { onSelect(text) }, colors = CardDefaults.cardColors( containerColor = Color(0xFF1E293B) ), shape = RoundedCornerShape(12.dp) ) { Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( Icons.Default.Star, contentDescription = null, tint = Color(0xFF6366F1), modifier = Modifier.size(16.dp) ) Spacer(modifier = Modifier.width(8.dp)) Text( text, color = Color.White, style = MaterialTheme.typography.bodyMedium ) } } } @Composable fun StatItem(label: String, value: String, color: Color) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(8.dp) ) { Text( value, style = MaterialTheme.typography.headlineMedium, color = color, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(4.dp)) Text( label, style = MaterialTheme.typography.bodySmall, color = Color(0xFF94A3B8) ) } } @Composable fun ArchiveScreen( notes: List, categories: List, onRestore: (Note) -> Unit, onDelete: (Note) -> Unit ) { if (notes.isEmpty()) { EmptyState( icon = Icons.Default.Archive, message = "Arsip kosong", subtitle = "Catatan yang diarsipkan akan muncul di sini" ) } else { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(notes) { note -> val category = categories.find { it.id == note.categoryId } ArchiveNoteCard( note = note, categoryName = category?.name ?: "Unknown", onRestore = { onRestore(note) }, onDelete = { onDelete(note) } ) } } } } @Composable fun ArchiveNoteCard( note: Note, categoryName: String, onRestore: () -> Unit, onDelete: () -> Unit ) { 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)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Column(modifier = Modifier.weight(1f)) { 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, color = Color(0xFF94A3B8), style = MaterialTheme.typography.bodyMedium ) } Row( modifier = Modifier .fillMaxWidth() .padding(top = 12.dp), horizontalArrangement = Arrangement.End ) { TextButton(onClick = onRestore) { Icon( Icons.Default.AccountBox, 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 = onDelete) { Icon( Icons.Default.Delete, contentDescription = null, modifier = Modifier.size(18.dp), tint = Color(0xFFEF4444) ) Spacer(modifier = Modifier.width(4.dp)) Text("Hapus", color = Color(0xFFEF4444), fontWeight = FontWeight.Bold) } } } } } @Composable fun TrashScreen( notes: List, categories: List, onRestore: (Note) -> Unit, onDeletePermanent: (Note) -> Unit ) { if (notes.isEmpty()) { EmptyState( icon = Icons.Default.Delete, message = "Sampah kosong", subtitle = "Catatan yang dihapus akan muncul di sini" ) } else { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(notes) { note -> val category = categories.find { it.id == note.categoryId } TrashNoteCard( note = note, categoryName = category?.name ?: "Unknown", onRestore = { onRestore(note) }, onDeletePermanent = { onDeletePermanent(note) } ) } } } } @Composable fun TrashNoteCard( note: Note, categoryName: String, onRestore: () -> Unit, onDeletePermanent: () -> Unit ) { Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( containerColor = Color(0xFF7F1D1D).copy(0.2f) ), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Column(modifier = Modifier.padding(16.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Column(modifier = Modifier.weight(1f)) { 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, color = Color(0xFF94A3B8), style = MaterialTheme.typography.bodyMedium ) } Row( modifier = Modifier .fillMaxWidth() .padding(top = 12.dp), horizontalArrangement = Arrangement.End ) { TextButton(onClick = onRestore) { Icon( Icons.Default.AccountBox, 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 = onDeletePermanent) { 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) } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun StarredNotesScreen( notes: List, categories: List, 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, message: String, subtitle: String ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(32.dp) ) { Icon( icon, contentDescription = null, modifier = Modifier.size(80.dp), tint = Color(0xFF475569) ) Spacer(modifier = Modifier.height(16.dp)) Text( message, style = MaterialTheme.typography.titleLarge, color = Color(0xFF94A3B8), fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) Text( subtitle, style = MaterialTheme.typography.bodyMedium, color = Color(0xFF64748B) ) } } }