Migrasi Components & Screen
This commit is contained in:
parent
0d22d94905
commit
63b10a3e1c
File diff suppressed because it is too large
Load Diff
@ -38,8 +38,6 @@ import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.MenuItem
|
||||
import com.example.notesai.util.Constants.AppColors.Divider
|
||||
|
||||
@Composable
|
||||
fun DrawerMenu(
|
||||
|
||||
@ -0,0 +1,528 @@
|
||||
package com.example.notesai.presentation.screens.ai
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Category
|
||||
import com.example.notesai.ChatMessage
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.config.APIKey
|
||||
import com.example.notesai.presentation.screens.ai.components.ChatBubble
|
||||
import com.example.notesai.presentation.screens.ai.components.CompactStatItem
|
||||
import com.example.notesai.presentation.screens.ai.components.SuggestionChip
|
||||
import com.example.notesai.util.Constants.AppColors.Divider
|
||||
import com.google.ai.client.generativeai.GenerativeModel
|
||||
import com.google.ai.client.generativeai.type.generationConfig
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.plus
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AIHelperScreen(
|
||||
categories: List<Category>,
|
||||
notes: List<Note>
|
||||
) {
|
||||
var prompt by remember { mutableStateOf("") }
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
var selectedCategory by remember { mutableStateOf<Category?>(null) }
|
||||
var showCategoryDropdown by remember { mutableStateOf(false) }
|
||||
var errorMessage by remember { mutableStateOf("") }
|
||||
var chatMessages by remember { mutableStateOf(listOf<ChatMessage>()) }
|
||||
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.Companion
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
// Header
|
||||
Card(
|
||||
modifier = Modifier.Companion.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.Companion.Transparent),
|
||||
shape = RoundedCornerShape(0.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.Companion.linearGradient(
|
||||
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
|
||||
)
|
||||
)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Column {
|
||||
Row(verticalAlignment = Alignment.Companion.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Default.Star,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFFBBF24),
|
||||
modifier = Modifier.Companion.size(28.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(12.dp))
|
||||
Column {
|
||||
Text(
|
||||
"AI Helper",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = Color.Companion.White,
|
||||
fontWeight = FontWeight.Companion.Bold
|
||||
)
|
||||
Text(
|
||||
"Powered by Gemini AI",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Companion.White.copy(0.8f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Category Selector & Stats - Compact Version
|
||||
Column(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Category Selector
|
||||
Box {
|
||||
Card(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.clickable { showCategoryDropdown = !showCategoryDropdown },
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFF1E293B)),
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Companion.CenterVertically
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Companion.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Default.Folder,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF6366F1),
|
||||
modifier = Modifier.Companion.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(8.dp))
|
||||
Text(
|
||||
selectedCategory?.name ?: "Semua Kategori",
|
||||
color = Color.Companion.White,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
Icons.Default.ArrowDropDown,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF94A3B8)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showCategoryDropdown,
|
||||
onDismissRequest = { showCategoryDropdown = false },
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.background(Color(0xFF1E293B))
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Semua Kategori", color = Color.Companion.White) },
|
||||
onClick = {
|
||||
selectedCategory = null
|
||||
showCategoryDropdown = false
|
||||
}
|
||||
)
|
||||
categories.forEach { category ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(category.name, color = Color.Companion.White) },
|
||||
onClick = {
|
||||
selectedCategory = category
|
||||
showCategoryDropdown = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats - Compact
|
||||
Spacer(modifier = Modifier.Companion.height(12.dp))
|
||||
val filteredNotes = if (selectedCategory != null) {
|
||||
notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived }
|
||||
} else {
|
||||
notes.filter { !it.isArchived }
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.Companion.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.Companion
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
if (chatMessages.isEmpty()) {
|
||||
// Welcome State
|
||||
Column(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxSize()
|
||||
.padding(32.dp),
|
||||
horizontalAlignment = Alignment.Companion.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Star,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.Companion.size(64.dp),
|
||||
tint = Color(0xFF6366F1).copy(0.5f)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.height(16.dp))
|
||||
Text(
|
||||
"Mulai Percakapan",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = Color.Companion.White,
|
||||
fontWeight = FontWeight.Companion.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.height(8.dp))
|
||||
Text(
|
||||
"Tanyakan apa saja tentang catatan Anda",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = Color(0xFF94A3B8),
|
||||
textAlign = TextAlign.Companion.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.Companion.height(24.dp))
|
||||
|
||||
// Suggestion Chips
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Companion.Start,
|
||||
modifier = Modifier.Companion.fillMaxWidth(0.8f)
|
||||
) {
|
||||
Text(
|
||||
"Contoh pertanyaan:",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFF64748B),
|
||||
modifier = Modifier.Companion.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.Companion
|
||||
.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.Companion.height(12.dp))
|
||||
}
|
||||
|
||||
// Loading Indicator
|
||||
if (isLoading) {
|
||||
Row(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color(0xFF1E293B)
|
||||
),
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.Companion.padding(16.dp),
|
||||
verticalAlignment = Alignment.Companion.CenterVertically
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.Companion.size(20.dp),
|
||||
color = Color(0xFF6366F1),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(12.dp))
|
||||
Text(
|
||||
"AI sedang berpikir...",
|
||||
color = Color(0xFF94A3B8),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error Message
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
Card(
|
||||
modifier = Modifier.Companion.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color(0xFFEF4444).copy(0.2f)
|
||||
),
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.Companion.padding(12.dp),
|
||||
verticalAlignment = Alignment.Companion.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFEF4444),
|
||||
modifier = Modifier.Companion.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(8.dp))
|
||||
Text(
|
||||
errorMessage,
|
||||
color = Color(0xFFEF4444),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.Companion.height(80.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Area
|
||||
Card(
|
||||
modifier = Modifier.Companion.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color(0xFF1E293B)
|
||||
),
|
||||
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.Companion.Bottom
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = prompt,
|
||||
onValueChange = { prompt = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
"Ketik pesan...",
|
||||
color = Color(0xFF64748B)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.Companion
|
||||
.weight(1f)
|
||||
.heightIn(min = 48.dp, max = 120.dp),
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedTextColor = Color.Companion.White,
|
||||
unfocusedTextColor = Color.Companion.White,
|
||||
focusedContainerColor = Color(0xFF334155),
|
||||
unfocusedContainerColor = Color(0xFF334155),
|
||||
cursorColor = Color(0xFFA855F7),
|
||||
focusedIndicatorColor = Color(0xFF6366F1),
|
||||
unfocusedIndicatorColor = Color(0xFF475569)
|
||||
),
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(24.dp),
|
||||
maxLines = 4
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.Companion.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.Companion.Transparent,
|
||||
modifier = Modifier.Companion
|
||||
.size(48.dp)
|
||||
.background(
|
||||
brush = Brush.Companion.linearGradient(
|
||||
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
|
||||
),
|
||||
shape = CircleShape
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Send,
|
||||
contentDescription = "Send",
|
||||
tint = Color.Companion.White,
|
||||
modifier = Modifier.Companion.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
package com.example.notesai.presentation.screens.ai.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.notesai.ChatMessage
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.example.notesai.presentation.screens.ai.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.example.notesai.presentation.screens.ai.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.example.notesai.presentation.screens.ai.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.example.notesai.presentation.screens.archive
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Category
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.presentation.components.EmptyState
|
||||
import com.example.notesai.presentation.screens.archive.components.ArchiveNoteCard
|
||||
|
||||
@Composable
|
||||
fun ArchiveScreen(
|
||||
notes: List<Note>,
|
||||
categories: List<Category>,
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package com.example.notesai.presentation.screens.archive.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountBox
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
package com.example.notesai.presentation.screens.main
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Create
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Category
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.presentation.components.EmptyState
|
||||
import com.example.notesai.presentation.screens.main.components.CategoryCard
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
categories: List<Category>,
|
||||
notes: List<Note>,
|
||||
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<Note> { 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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NoteCard(note: Note, onClick: () -> Unit, onPinClick: () -> Unit) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
package com.example.notesai.presentation.screens.main.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Category
|
||||
|
||||
@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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package com.example.notesai.presentation.screens.main.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.outlined.StarBorder
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.util.Constants.AppColors.Divider
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,251 @@
|
||||
package com.example.notesai.presentation.screens.note
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.outlined.StarBorder
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.util.Constants.AppColors.Divider
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
package com.example.notesai.presentation.screens.starred
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.outlined.StarBorder
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Note
|
||||
|
||||
@Composable
|
||||
fun StarredNoteCard(
|
||||
note: Note,
|
||||
categoryName: String,
|
||||
onClick: () -> Unit,
|
||||
onUnpin: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color(0xFF1E293B)
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.Companion.padding(16.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.Companion.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Companion.Top
|
||||
) {
|
||||
Column(modifier = Modifier.Companion.weight(1f)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Companion.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Star,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFFBBF24),
|
||||
modifier = Modifier.Companion.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(8.dp))
|
||||
Text(
|
||||
note.title,
|
||||
fontWeight = FontWeight.Companion.Bold,
|
||||
color = Color.Companion.White,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.Companion.height(4.dp))
|
||||
Text(
|
||||
categoryName,
|
||||
color = Color(0xFF64748B),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (note.content.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.Companion.height(8.dp))
|
||||
Text(
|
||||
note.content,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Companion.Ellipsis,
|
||||
color = Color(0xFF94A3B8),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.padding(top = 12.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = onClick) {
|
||||
Icon(
|
||||
Icons.Default.Info,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.Companion.size(18.dp),
|
||||
tint = Color(0xFF6366F1)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(4.dp))
|
||||
Text(
|
||||
"Lihat Detail",
|
||||
color = Color(0xFF6366F1),
|
||||
fontWeight = FontWeight.Companion.Bold
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.Companion.width(8.dp))
|
||||
TextButton(onClick = onUnpin) {
|
||||
Icon(
|
||||
Icons.Outlined.StarBorder,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.Companion.size(18.dp),
|
||||
tint = Color(0xFFFBBF24)
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(4.dp))
|
||||
Text(
|
||||
"Hapus Bintang",
|
||||
color = Color(0xFFFBBF24),
|
||||
fontWeight = FontWeight.Companion.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.example.notesai.presentation.screens.starred.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Category
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.presentation.components.EmptyState
|
||||
import com.example.notesai.presentation.screens.starred.StarredNoteCard
|
||||
|
||||
@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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package com.example.notesai.presentation.screens.trash.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountBox
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.example.notesai.presentation.screens.trash.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.notesai.Category
|
||||
import com.example.notesai.Note
|
||||
import com.example.notesai.presentation.components.EmptyState
|
||||
|
||||
@Composable
|
||||
fun TrashScreen(
|
||||
notes: List<Note>,
|
||||
categories: List<Category>,
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user