diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d2413f1..0668492 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,7 +7,7 @@ plugins { android { namespace = "com.example.notesai" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.example.notesai" @@ -64,6 +64,9 @@ dependencies { implementation("androidx.datastore:datastore-preferences:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") implementation("com.google.ai.client.generativeai:generativeai:0.9.0") + implementation(libs.androidx.ui.text) + implementation(libs.androidx.material3) + implementation(libs.androidx.animation.core) // Untuk integrasi Gemini AI (optional - uncomment jika sudah ada API key) // implementation("com.google.ai.client.generativeai:generativeai:0.1.2") diff --git a/app/src/main/java/com/example/notesai/presentation/screens/ai/AIHelperScreen.kt b/app/src/main/java/com/example/notesai/presentation/screens/ai/AIHelperScreen.kt index 9e55281..01b65d4 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/ai/AIHelperScreen.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/ai/AIHelperScreen.kt @@ -1,71 +1,41 @@ package com.example.notesai.presentation.screens.ai +import androidx.compose.animation.* +import androidx.compose.animation.core.* 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.layout.* 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.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.graphics.Brush +import androidx.compose.ui.draw.clip 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.data.model.Note -import com.example.notesai.data.model.ChatMessage +import androidx.compose.ui.unit.sp import com.example.notesai.data.model.Category -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.example.notesai.data.model.ChatMessage +import com.example.notesai.data.model.Note +import com.example.notesai.util.Constants 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 +import java.text.SimpleDateFormat +import java.util.* +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.presentation.screens.ai.components.StatItem @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -86,11 +56,11 @@ fun AIHelperScreen( val clipboardManager = LocalClipboardManager.current val scrollState = rememberScrollState() -// Inisialisasi Gemini Model + // Inisialisasi Gemini Model val generativeModel = remember { GenerativeModel( - modelName = "gemini-2.5-flash", - apiKey = APIKey.GEMINI_API_KEY, + modelName = "gemini-2.0-flash-exp", + apiKey = com.example.notesai.config.APIKey.GEMINI_API_KEY, generationConfig = generationConfig { temperature = 0.8f topK = 40 @@ -110,217 +80,197 @@ fun AIHelperScreen( } Column( - modifier = Modifier.Companion + modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background) + .background(Constants.AppColors.Background) ) { - // Header - Card( - modifier = Modifier.Companion.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = Color.Companion.Transparent), - shape = RoundedCornerShape(0.dp) + // Category Selector & Stats - Compact + Surface( + color = Constants.AppColors.Surface, + shadowElevation = 2.dp ) { - Box( - modifier = Modifier.Companion + Column( + modifier = Modifier .fillMaxWidth() - .background( - brush = Brush.Companion.linearGradient( - colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) - ) - ) - .padding(20.dp) + .padding(16.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 + // Category Selector + Box { + Card( + onClick = { showCategoryDropdown = !showCategoryDropdown }, + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = Constants.AppColors.SurfaceVariant + ), + shape = RoundedCornerShape(12.dp) ) { - Row(verticalAlignment = Alignment.Companion.CenterVertically) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + Icons.Default.Folder, + contentDescription = null, + tint = Constants.AppColors.Primary, + modifier = Modifier.size(20.dp) + ) + Text( + selectedCategory?.name ?: "Semua Kategori", + color = Constants.AppColors.OnSurface, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium + ) + } Icon( - Icons.Default.Folder, + Icons.Default.ArrowDropDown, 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 + tint = Constants.AppColors.OnSurfaceVariant ) } - 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 -> + DropdownMenu( + expanded = showCategoryDropdown, + onDismissRequest = { showCategoryDropdown = false }, + modifier = Modifier + .fillMaxWidth(0.9f) + .background(Constants.AppColors.SurfaceElevated) + ) { DropdownMenuItem( - text = { Text(category.name, color = Color.Companion.White) }, + text = { Text("Semua Kategori", color = Constants.AppColors.OnSurface) }, onClick = { - selectedCategory = category + selectedCategory = null showCategoryDropdown = false } ) + categories.forEach { category -> + DropdownMenuItem( + text = { Text(category.name, color = Constants.AppColors.OnSurface) }, + 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 } - } + Spacer(modifier = Modifier.height(12.dp)) - 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) - ) + // Stats - Compact + 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( + icon = Icons.Default.Description, + value = filteredNotes.size.toString(), + label = "Catatan" + ) + CompactStatItem( + icon = Icons.Default.Star, + value = filteredNotes.count { it.isPinned }.toString(), + label = "Dipasang" + ) + CompactStatItem( + icon = Icons.Default.Folder, + value = categories.size.toString(), + label = "Kategori" + ) + } } } - Divider(color = Color(0xFF334155), thickness = 1.dp) + HorizontalDivider(color = Constants.AppColors.Divider) // Chat Area - Column( - modifier = Modifier.Companion + Box( + modifier = Modifier .weight(1f) .fillMaxWidth() ) { if (chatMessages.isEmpty()) { // Welcome State Column( - modifier = Modifier.Companion + modifier = Modifier .fillMaxSize() .padding(32.dp), - horizontalAlignment = Alignment.Companion.CenterHorizontally, + horizontalAlignment = Alignment.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)) + Box( + modifier = Modifier + .size(80.dp) + .background( + color = Constants.AppColors.Primary.copy(alpha = 0.1f), + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Icon( + Icons.Default.AutoAwesome, + contentDescription = null, + modifier = Modifier.size(40.dp), + tint = Constants.AppColors.Primary + ) + } + + Spacer(modifier = Modifier.height(24.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 + "AI Assistant", + style = MaterialTheme.typography.headlineMedium, + color = Constants.AppColors.OnBackground, + fontWeight = FontWeight.Bold ) - Spacer(modifier = Modifier.Companion.height(24.dp)) + Spacer(modifier = Modifier.height(8.dp)) + + Text( + "Tanyakan apa saja tentang catatan Anda", + style = MaterialTheme.typography.bodyLarge, + color = Constants.AppColors.OnSurfaceVariant, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(32.dp)) // Suggestion Chips Column( - horizontalAlignment = Alignment.Companion.Start, - modifier = Modifier.Companion.fillMaxWidth(0.8f) + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth(0.85f) ) { Text( "Contoh pertanyaan:", - style = MaterialTheme.typography.bodySmall, - color = Color(0xFF64748B), - modifier = Modifier.Companion.padding(bottom = 8.dp) + style = MaterialTheme.typography.labelMedium, + color = Constants.AppColors.OnSurfaceTertiary ) - SuggestionChip("Analisis catatan saya", onSelect = { prompt = it }) - SuggestionChip("Buat ringkasan", onSelect = { prompt = it }) - SuggestionChip("Berikan saran organisasi", onSelect = { prompt = it }) + SuggestionChip("Analisis catatan saya") { prompt = it } + SuggestionChip("Buat ringkasan") { prompt = it } + SuggestionChip("Berikan saran organisasi") { prompt = it } } } } else { // Chat Messages Column( - modifier = Modifier.Companion + modifier = Modifier .fillMaxSize() .verticalScroll(scrollState) - .padding(16.dp) + .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 100.dp) ) { chatMessages.forEach { message -> ChatBubble( @@ -336,36 +286,34 @@ fun AIHelperScreen( }, showCopied = showCopiedMessage && copiedMessageId == message.id ) - Spacer(modifier = Modifier.Companion.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) } // Loading Indicator if (isLoading) { Row( - modifier = Modifier.Companion + modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), horizontalArrangement = Arrangement.Start ) { - Card( - colors = CardDefaults.cardColors( - containerColor = Color(0xFF1E293B) - ), - shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp) + Surface( + color = Constants.AppColors.SurfaceVariant, + shape = RoundedCornerShape(16.dp) ) { Row( - modifier = Modifier.Companion.padding(16.dp), - verticalAlignment = Alignment.Companion.CenterVertically + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CircularProgressIndicator( - modifier = Modifier.Companion.size(20.dp), - color = Color(0xFF6366F1), + modifier = Modifier.size(20.dp), + color = Constants.AppColors.Primary, strokeWidth = 2.dp ) - Spacer(modifier = Modifier.Companion.width(12.dp)) Text( "AI sedang berpikir...", - color = Color(0xFF94A3B8), + color = Constants.AppColors.OnSurfaceVariant, style = MaterialTheme.typography.bodyMedium ) } @@ -375,52 +323,46 @@ fun AIHelperScreen( // 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) + Surface( + modifier = Modifier.fillMaxWidth(), + color = Constants.AppColors.Error.copy(alpha = 0.1f), + shape = RoundedCornerShape(12.dp) ) { Row( - modifier = Modifier.Companion.padding(12.dp), - verticalAlignment = Alignment.Companion.CenterVertically + modifier = Modifier.padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( Icons.Default.Warning, contentDescription = null, - tint = Color(0xFFEF4444), - modifier = Modifier.Companion.size(20.dp) + tint = Constants.AppColors.Error, + modifier = Modifier.size(20.dp) ) - Spacer(modifier = Modifier.Companion.width(8.dp)) Text( errorMessage, - color = Color(0xFFEF4444), + color = Constants.AppColors.Error, 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) + // Input Area - Minimalist + Surface( + color = Constants.AppColors.Surface, + shadowElevation = 8.dp, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) ) { Row( - modifier = Modifier.Companion + modifier = Modifier .fillMaxWidth() .padding(16.dp), - verticalAlignment = Alignment.Companion.Bottom + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { OutlinedTextField( value = prompt, @@ -428,33 +370,30 @@ fun AIHelperScreen( placeholder = { Text( "Ketik pesan...", - color = Color(0xFF64748B) + color = Constants.AppColors.OnSurfaceTertiary ) }, - modifier = Modifier.Companion + modifier = Modifier .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) + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Constants.AppColors.OnBackground, + unfocusedTextColor = Constants.AppColors.OnSurface, + focusedContainerColor = Constants.AppColors.SurfaceVariant, + unfocusedContainerColor = Constants.AppColors.SurfaceVariant, + cursorColor = Constants.AppColors.Primary, + focusedBorderColor = Constants.AppColors.Primary, + unfocusedBorderColor = Color.Transparent ), - shape = androidx.compose.foundation.shape.RoundedCornerShape(24.dp), + 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 @@ -485,18 +424,14 @@ fun AIHelperScreen( } } - val fullPrompt = - "$notesContext\n\nPertanyaan: $userPrompt\n\nBerikan jawaban dalam bahasa Indonesia yang natural dan membantu." - + 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 { @@ -505,21 +440,14 @@ fun AIHelperScreen( } } }, - containerColor = Color.Companion.Transparent, - modifier = Modifier.Companion - .size(48.dp) - .background( - brush = Brush.Companion.linearGradient( - colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7)) - ), - shape = CircleShape - ) + containerColor = Constants.AppColors.Primary, + modifier = Modifier.size(48.dp) ) { Icon( Icons.Default.Send, contentDescription = "Send", - tint = Color.Companion.White, - modifier = Modifier.Companion.size(24.dp) + tint = Color.White, + modifier = Modifier.size(20.dp) ) } } diff --git a/app/src/main/java/com/example/notesai/presentation/screens/ai/components/ChatBubble.kt b/app/src/main/java/com/example/notesai/presentation/screens/ai/components/ChatBubble.kt index 54b3e4b..1a62d24 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/ai/components/ChatBubble.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/ai/components/ChatBubble.kt @@ -1,11 +1,16 @@ package com.example.notesai.presentation.screens.ai.components +import androidx.compose.foundation.background 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.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AutoAwesome @@ -15,6 +20,7 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -24,6 +30,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.notesai.data.model.ChatMessage +import com.example.notesai.util.Constants import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -41,28 +48,34 @@ fun ChatBubble( 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 + Box( modifier = Modifier .size(32.dp) - .padding(end = 8.dp) - ) + .background( + color = Constants.AppColors.Primary.copy(alpha = 0.1f), + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Icon( + Icons.Default.AutoAwesome, + contentDescription = null, + tint = Constants.AppColors.Primary, + modifier = Modifier.size(16.dp) + ) + } + Spacer(modifier = Modifier.width(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) - ), + Surface( + color = if (message.isUser) + Constants.AppColors.Primary + else + Constants.AppColors.SurfaceVariant, shape = RoundedCornerShape( topStart = 16.dp, topEnd = 16.dp, @@ -73,7 +86,7 @@ fun ChatBubble( Column(modifier = Modifier.padding(12.dp)) { Text( message.message, - color = Color.White, + color = if (message.isUser) Color.White else Constants.AppColors.OnSurface, style = MaterialTheme.typography.bodyMedium, lineHeight = 20.sp ) @@ -85,21 +98,25 @@ fun ChatBubble( ) { Text( dateFormat.format(Date(message.timestamp)), - color = Color.White.copy(0.6f), + color = if (message.isUser) + Color.White.copy(0.7f) + else + Constants.AppColors.OnSurfaceTertiary, style = MaterialTheme.typography.bodySmall, + fontSize = 11.sp, modifier = Modifier.padding(top = 4.dp) ) if (!message.isUser) { IconButton( onClick = onCopy, - modifier = Modifier.size(32.dp) + modifier = Modifier.size(28.dp) ) { Icon( Icons.Default.ContentCopy, contentDescription = "Copy", - tint = Color.White.copy(0.7f), - modifier = Modifier.size(16.dp) + tint = Constants.AppColors.OnSurfaceVariant, + modifier = Modifier.size(14.dp) ) } } @@ -110,8 +127,9 @@ fun ChatBubble( if (showCopied && !message.isUser) { Text( "✓ Disalin", - color = Color(0xFF10B981), + color = Constants.AppColors.Success, style = MaterialTheme.typography.bodySmall, + fontSize = 11.sp, modifier = Modifier.padding(top = 4.dp, start = 8.dp) ) } diff --git a/app/src/main/java/com/example/notesai/presentation/screens/ai/components/CompactStatItem.kt b/app/src/main/java/com/example/notesai/presentation/screens/ai/components/CompactStatItem.kt index 9a89728..a0e8720 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/ai/components/CompactStatItem.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/ai/components/CompactStatItem.kt @@ -1,11 +1,15 @@ package com.example.notesai.presentation.screens.ai.components import androidx.compose.foundation.background +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.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -14,29 +18,45 @@ 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.util.Constants @Composable -fun CompactStatItem(label: String, value: String, color: Color) { +fun CompactStatItem( + icon: androidx.compose.ui.graphics.vector.ImageVector, + value: String, + label: String +) { Row( verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier .background( - color = Color(0xFF1E293B), + color = Constants.AppColors.SurfaceVariant, 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) + Icon( + icon, + contentDescription = null, + tint = Constants.AppColors.Primary, + modifier = Modifier.size(16.dp) ) + Column { + Text( + value, + style = MaterialTheme.typography.titleMedium, + color = Constants.AppColors.OnBackground, + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + Text( + label, + style = MaterialTheme.typography.bodySmall, + color = Constants.AppColors.OnSurfaceTertiary, + fontSize = 11.sp + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/notesai/presentation/screens/ai/components/SuggestionChip.kt b/app/src/main/java/com/example/notesai/presentation/screens/ai/components/SuggestionChip.kt index 63dd02e..e46fe36 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/ai/components/SuggestionChip.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/ai/components/SuggestionChip.kt @@ -1,6 +1,7 @@ package com.example.notesai.presentation.screens.ai.components import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding @@ -8,44 +9,48 @@ 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.Lightbulb 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.Surface 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 +import androidx.compose.ui.unit.sp +import com.example.notesai.util.Constants @Composable -fun SuggestionChip(text: String, onSelect: (String) -> Unit) { - Card( - modifier = Modifier - .padding(vertical = 4.dp) - .clickable { onSelect(text) }, - colors = CardDefaults.cardColors( - containerColor = Color(0xFF1E293B) - ), +fun SuggestionChip( + text: String, + onSelect: (String) -> Unit +) { + Surface( + onClick = { onSelect(text) }, + color = Constants.AppColors.SurfaceVariant, shape = RoundedCornerShape(12.dp) ) { Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( - Icons.Default.Star, + Icons.Default.Lightbulb, contentDescription = null, - tint = Color(0xFF6366F1), + tint = Constants.AppColors.Primary, modifier = Modifier.size(16.dp) ) - Spacer(modifier = Modifier.width(8.dp)) Text( text, - color = Color.White, - style = MaterialTheme.typography.bodyMedium + color = Constants.AppColors.OnSurface, + style = MaterialTheme.typography.bodyMedium, + fontSize = 14.sp ) } } diff --git a/app/src/main/java/com/example/notesai/presentation/screens/main/components/CategoryCard.kt b/app/src/main/java/com/example/notesai/presentation/screens/main/components/CategoryCard.kt index ca67220..ee40363 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/main/components/CategoryCard.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/main/components/CategoryCard.kt @@ -1,21 +1,28 @@ +// File: presentation/screens/main/components/CategoryCard.kt package com.example.notesai.presentation.screens.main.components +import androidx.compose.animation.* +import androidx.compose.animation.core.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.FolderOpen 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.scale 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 androidx.compose.ui.unit.sp import com.example.notesai.data.model.Category +import com.example.notesai.util.Constants @Composable fun CategoryCard( @@ -28,16 +35,33 @@ fun CategoryCard( var showDeleteConfirm by remember { mutableStateOf(false) } var showEditDialog by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) } + var isPressed by remember { mutableStateOf(false) } + + // Smooth scale animation + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.95f else 1f, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ), + label = "scale" + ) // Delete Confirmation Dialog if (showDeleteConfirm) { AlertDialog( onDismissRequest = { showDeleteConfirm = false }, - title = { Text("Pindahkan ke Sampah?", color = Color.White) }, + title = { + Text( + "Pindahkan ke Sampah?", + color = Constants.AppColors.OnBackground, + fontWeight = FontWeight.Bold + ) + }, text = { Text( - "Kategori '${category.name}' dan semua catatan di dalamnya akan dipindahkan ke sampah.", - color = Color.White + "Kategori '${category.name}' dan $noteCount catatan di dalamnya akan dipindahkan ke sampah.", + color = Constants.AppColors.OnSurfaceVariant ) }, confirmButton = { @@ -47,23 +71,19 @@ fun CategoryCard( showDeleteConfirm = false }, colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFFEF4444) + containerColor = Constants.AppColors.Error ) ) { Text("Hapus", color = Color.White) } }, dismissButton = { - Button( - onClick = { showDeleteConfirm = false }, - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF64748B) - ) - ) { - Text("Batal", color = Color.White) + TextButton(onClick = { showDeleteConfirm = false }) { + Text("Batal", color = Constants.AppColors.OnSurfaceVariant) } }, - containerColor = Color(0xFF1E293B) + containerColor = Constants.AppColors.Surface, + shape = RoundedCornerShape(Constants.Radius.Large.dp) ) } @@ -79,13 +99,19 @@ fun CategoryCard( ) } + // Main Card - Minimalist Design Card( modifier = Modifier .fillMaxWidth() + .scale(scale) .clickable(onClick = onClick), - shape = RoundedCornerShape(20.dp), - colors = CardDefaults.cardColors(containerColor = Color.Transparent), - elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + shape = RoundedCornerShape(Constants.Radius.Large.dp), + colors = CardDefaults.cardColors( + containerColor = Constants.AppColors.Surface + ), + elevation = CardDefaults.cardElevation( + defaultElevation = Constants.Elevation.Small.dp + ) ) { Box( modifier = Modifier @@ -93,94 +119,146 @@ fun CategoryCard( .background( brush = Brush.linearGradient( colors = listOf( - Color(category.gradientStart), - Color(category.gradientEnd) + Color(category.gradientStart).copy(alpha = 0.1f), + Color(category.gradientEnd).copy(alpha = 0.05f) ) ) ) - .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) - ) - } - - // Menu Button (Titik Tiga) - Box( - modifier = Modifier.align(Alignment.TopEnd) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Spacing.Large.dp) ) { - IconButton( - onClick = { showMenu = true } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top ) { - Icon( - Icons.Default.MoreVert, - contentDescription = "Menu", - tint = Color.White.copy(0.9f) - ) + // Icon dengan gradient accent + Box( + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(Constants.Radius.Medium.dp)) + .background( + brush = Brush.linearGradient( + colors = listOf( + Color(category.gradientStart).copy(alpha = 0.2f), + Color(category.gradientEnd).copy(alpha = 0.1f) + ) + ) + ), + contentAlignment = Alignment.Center + ) { + Icon( + Icons.Outlined.FolderOpen, + contentDescription = null, + tint = Color(category.gradientStart), + modifier = Modifier.size(24.dp) + ) + } + + // Menu Button + Box { + IconButton( + onClick = { showMenu = true }, + modifier = Modifier.size(32.dp) + ) { + Icon( + Icons.Default.MoreVert, + contentDescription = "Menu", + tint = Constants.AppColors.OnSurfaceVariant, + modifier = Modifier.size(20.dp) + ) + } + + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + modifier = Modifier.background(Constants.AppColors.SurfaceElevated) + ) { + DropdownMenuItem( + text = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp) + ) { + Icon( + Icons.Default.Edit, + contentDescription = null, + tint = Constants.AppColors.Primary, + modifier = Modifier.size(18.dp) + ) + Text( + "Edit Kategori", + color = Constants.AppColors.OnSurface, + fontSize = 14.sp + ) + } + }, + onClick = { + showMenu = false + showEditDialog = true + } + ) + + DropdownMenuItem( + text = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp) + ) { + Icon( + Icons.Default.Delete, + contentDescription = null, + tint = Constants.AppColors.Error, + modifier = Modifier.size(18.dp) + ) + Text( + "Pindah ke Sampah", + color = Constants.AppColors.OnSurface, + fontSize = 14.sp + ) + } + }, + onClick = { + showMenu = false + showDeleteConfirm = true + } + ) + } + } } - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false }, - modifier = Modifier.background(Color(0xFF1E293B)) - ) { - DropdownMenuItem( - text = { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - Icons.Default.Edit, - contentDescription = null, - tint = Color(0xFF6366F1), - modifier = Modifier.size(20.dp) - ) - Text("Edit Kategori", color = Color.White) - } - }, - onClick = { - showMenu = false - showEditDialog = true - } - ) + Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp)) - DropdownMenuItem( - text = { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - Icons.Default.Delete, - contentDescription = null, - tint = Color(0xFFEF4444), - modifier = Modifier.size(20.dp) - ) - Text("Pindah ke Sampah", color = Color.White) - } - }, - onClick = { - showMenu = false - showDeleteConfirm = true - } + // Category Name + Text( + category.name, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = Constants.AppColors.OnBackground, + fontSize = 18.sp + ) + + Spacer(modifier = Modifier.height(Constants.Spacing.ExtraSmall.dp)) + + // Note Count dengan subtle styling + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Icon( + Icons.Default.Description, + contentDescription = null, + tint = Constants.AppColors.OnSurfaceTertiary, + modifier = Modifier.size(14.dp) + ) + Text( + "$noteCount catatan", + style = MaterialTheme.typography.bodySmall, + color = Constants.AppColors.OnSurfaceTertiary, + fontSize = 13.sp ) } } @@ -194,31 +272,23 @@ fun EditCategoryDialog( onDismiss: () -> Unit, onSave: (String, Long, Long) -> Unit ) { - 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) - ) var name by remember { mutableStateOf(category.name) } var selectedGradient by remember { mutableStateOf( - gradients.indexOfFirst { + Constants.AppColors.CategoryColors.indexOfFirst { it.first == category.gradientStart && it.second == category.gradientEnd }.takeIf { it >= 0 } ?: 0 ) } + AlertDialog( onDismissRequest = onDismiss, - containerColor = Color(0xFF1E293B), + containerColor = Constants.AppColors.Surface, + shape = RoundedCornerShape(Constants.Radius.Large.dp), title = { Text( "Edit Kategori", - color = Color.White, + color = Constants.AppColors.OnBackground, fontWeight = FontWeight.Bold ) }, @@ -227,42 +297,51 @@ fun EditCategoryDialog( OutlinedTextField( value = name, onValueChange = { name = it }, - label = { Text("Nama Kategori", color = Color(0xFF94A3B8)) }, + label = { + Text( + "Nama Kategori", + color = Constants.AppColors.OnSurfaceVariant + ) + }, 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) + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Constants.AppColors.OnBackground, + unfocusedTextColor = Constants.AppColors.OnSurface, + focusedContainerColor = Constants.AppColors.SurfaceVariant, + unfocusedContainerColor = Constants.AppColors.SurfaceVariant, + cursorColor = Constants.AppColors.Primary, + focusedBorderColor = Constants.AppColors.Primary, + unfocusedBorderColor = Constants.AppColors.Border ), - shape = RoundedCornerShape(12.dp) + shape = RoundedCornerShape(Constants.Radius.Medium.dp) ) Spacer(modifier = Modifier.height(20.dp)) + Text( - "Pilih Gradient:", + "Pilih Warna:", style = MaterialTheme.typography.bodyMedium, - color = Color.White, - fontWeight = FontWeight.SemiBold + color = Constants.AppColors.OnSurface, + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp ) Spacer(modifier = Modifier.height(12.dp)) - gradients.chunked(4).forEach { row -> + Constants.AppColors.CategoryColors.chunked(4).forEach { row -> Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { row.forEachIndexed { _, gradient -> - val globalIndex = gradients.indexOf(gradient) + val globalIndex = Constants.AppColors.CategoryColors.indexOf(gradient) + val isSelected = selectedGradient == globalIndex + Box( modifier = Modifier .weight(1f) .aspectRatio(1f) - .clip(RoundedCornerShape(12.dp)) + .clip(RoundedCornerShape(Constants.Radius.Medium.dp)) .background( brush = Brush.linearGradient( colors = listOf( @@ -274,7 +353,11 @@ fun EditCategoryDialog( .clickable { selectedGradient = globalIndex }, contentAlignment = Alignment.Center ) { - if (selectedGradient == globalIndex) { + this@Row.AnimatedVisibility( + visible = isSelected, + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut() + ) { Icon( Icons.Default.Check, contentDescription = null, @@ -293,19 +376,13 @@ fun EditCategoryDialog( Button( onClick = { if (name.isNotBlank()) { - val gradient = gradients[selectedGradient] + val gradient = Constants.AppColors.CategoryColors[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) + containerColor = Constants.AppColors.Primary ) ) { Text("Simpan", color = Color.White, fontWeight = FontWeight.Bold) @@ -313,7 +390,7 @@ fun EditCategoryDialog( }, dismissButton = { TextButton(onClick = onDismiss) { - Text("Batal", color = Color(0xFF94A3B8)) + Text("Batal", color = Constants.AppColors.OnSurfaceVariant) } } ) diff --git a/app/src/main/java/com/example/notesai/presentation/screens/main/components/NoteCard.kt b/app/src/main/java/com/example/notesai/presentation/screens/main/components/NoteCard.kt index b0862a1..0f0c6fd 100644 --- a/app/src/main/java/com/example/notesai/presentation/screens/main/components/NoteCard.kt +++ b/app/src/main/java/com/example/notesai/presentation/screens/main/components/NoteCard.kt @@ -1,38 +1,28 @@ +// File: presentation/screens/main/components/NoteCard.kt package com.example.notesai.presentation.screens.main.components +import androidx.compose.animation.core.* 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.layout.* 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.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.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale 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.data.model.Note +import com.example.notesai.util.Constants import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale +import java.util.* @OptIn(ExperimentalFoundationApi::class) @Composable @@ -43,86 +33,105 @@ fun NoteCard( ) { val dateFormat = SimpleDateFormat("dd MMM, HH:mm", Locale("id", "ID")) + // Scale animation on press + var isPressed by remember { mutableStateOf(false) } + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.95f else 1f, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ), + label = "scale" + ) + Card( modifier = Modifier .fillMaxWidth() - .combinedClickable( - onClick = onClick, - ), + .scale(scale) + .combinedClickable(onClick = onClick), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( - containerColor = Color(0xFF1E293B) + containerColor = Constants.AppColors.SurfaceVariant // Lebih terang dari Surface ), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + elevation = CardDefaults.cardElevation( + defaultElevation = 2.dp + ) ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { + // Header: Title + Pin Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top ) { - // Judul + // Title Text( note.title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, - color = Color.White, + color = Constants.AppColors.OnBackground, modifier = Modifier.weight(1f), maxLines = 2, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + fontSize = 18.sp ) + + // Pin Button IconButton( onClick = onPinClick, - modifier = Modifier.size(24.dp) + modifier = Modifier.size(32.dp) ) { Icon( if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder, contentDescription = "Pin", - tint = if (note.isPinned) Color(0xFFFBBF24) else Color.Gray, + tint = if (note.isPinned) Constants.AppColors.Warning else Constants.AppColors.OnSurfaceVariant, modifier = Modifier.size(18.dp) ) } } - // Deskripsi + // Content Preview 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 + color = Constants.AppColors.OnSurfaceVariant, + lineHeight = 20.sp, + fontSize = 14.sp ) } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) + // Divider HorizontalDivider( - color = Color(0xFF334155), + color = Constants.AppColors.Divider, thickness = 1.dp ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(12.dp)) - // Timestamp - Text( - dateFormat.format(Date(note.timestamp)), - style = MaterialTheme.typography.bodySmall, - color = Color(0xFF64748B) - ) + // Footer: Timestamp + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + dateFormat.format(Date(note.timestamp)), + style = MaterialTheme.typography.bodySmall, + color = Constants.AppColors.OnSurfaceTertiary, + fontSize = 12.sp + ) + } } } } \ No newline at end of file