Mengubah Warna dan menyesuaikan UI/UX Halaman AI Helper

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-12-13 23:15:24 +07:00
parent 0f0ac6b8f3
commit 80774b58ea
7 changed files with 563 additions and 503 deletions

View File

@ -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")

View File

@ -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
@ -89,8 +59,8 @@ fun AIHelperScreen(
// 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,94 +80,58 @@ 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
.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
modifier = Modifier
.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)
onClick = { showCategoryDropdown = !showCategoryDropdown },
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant
),
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier.Companion
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Companion.CenterVertically
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(verticalAlignment = Alignment.Companion.CenterVertically) {
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Color(0xFF6366F1),
modifier = Modifier.Companion.size(20.dp)
tint = Constants.AppColors.Primary,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.Companion.width(8.dp))
Text(
selectedCategory?.name ?: "Semua Kategori",
color = Color.Companion.White,
style = MaterialTheme.typography.bodyMedium
color = Constants.AppColors.OnSurface,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
}
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
tint = Color(0xFF94A3B8)
tint = Constants.AppColors.OnSurfaceVariant
)
}
}
@ -205,12 +139,12 @@ fun AIHelperScreen(
DropdownMenu(
expanded = showCategoryDropdown,
onDismissRequest = { showCategoryDropdown = false },
modifier = Modifier.Companion
.fillMaxWidth()
.background(Color(0xFF1E293B))
modifier = Modifier
.fillMaxWidth(0.9f)
.background(Constants.AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = { Text("Semua Kategori", color = Color.Companion.White) },
text = { Text("Semua Kategori", color = Constants.AppColors.OnSurface) },
onClick = {
selectedCategory = null
showCategoryDropdown = false
@ -218,7 +152,7 @@ fun AIHelperScreen(
)
categories.forEach { category ->
DropdownMenuItem(
text = { Text(category.name, color = Color.Companion.White) },
text = { Text(category.name, color = Constants.AppColors.OnSurface) },
onClick = {
selectedCategory = category
showCategoryDropdown = false
@ -228,8 +162,9 @@ fun AIHelperScreen(
}
}
Spacer(modifier = Modifier.height(12.dp))
// Stats - Compact
Spacer(modifier = Modifier.Companion.height(12.dp))
val filteredNotes = if (selectedCategory != null) {
notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived }
} else {
@ -237,90 +172,105 @@ fun AIHelperScreen(
}
Row(
modifier = Modifier.Companion.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
CompactStatItem(
label = "Total",
icon = Icons.Default.Description,
value = filteredNotes.size.toString(),
color = Color(0xFF6366F1)
label = "Catatan"
)
CompactStatItem(
label = "Dipasang",
icon = Icons.Default.Star,
value = filteredNotes.count { it.isPinned }.toString(),
color = Color(0xFFFBBF24)
label = "Dipasang"
)
CompactStatItem(
label = "Kategori",
icon = Icons.Default.Folder,
value = categories.size.toString(),
color = Color(0xFFA855F7)
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
) {
Box(
modifier = Modifier
.size(80.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Star,
Icons.Default.AutoAwesome,
contentDescription = null,
modifier = Modifier.Companion.size(64.dp),
tint = Color(0xFF6366F1).copy(0.5f)
modifier = Modifier.size(40.dp),
tint = Constants.AppColors.Primary
)
Spacer(modifier = Modifier.Companion.height(16.dp))
}
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)
)
}
}

View File

@ -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)
Surface(
color = if (message.isUser)
Constants.AppColors.Primary
else
Color(0xFF1E293B)
),
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)
)
}

View File

@ -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)
) {
Icon(
icon,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(16.dp)
)
Column {
Text(
value,
style = MaterialTheme.typography.titleMedium,
color = color,
fontWeight = FontWeight.Bold
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
Spacer(modifier = Modifier.width(4.dp))
Text(
label,
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF94A3B8)
color = Constants.AppColors.OnSurfaceTertiary,
fontSize = 11.sp
)
}
}
}

View File

@ -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
)
}
}

View File

@ -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,67 +119,81 @@ 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 {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(Constants.Spacing.Large.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
// 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.Default.Folder,
Icons.Outlined.FolderOpen,
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)
tint = Color(category.gradientStart),
modifier = Modifier.size(24.dp)
)
}
// Menu Button (Titik Tiga)
Box(
modifier = Modifier.align(Alignment.TopEnd)
) {
// Menu Button
Box {
IconButton(
onClick = { showMenu = true }
onClick = { showMenu = true },
modifier = Modifier.size(32.dp)
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Color.White.copy(0.9f)
tint = Constants.AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(Color(0xFF1E293B))
modifier = Modifier.background(Constants.AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = Color(0xFF6366F1),
modifier = Modifier.size(20.dp)
tint = Constants.AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Kategori",
color = Constants.AppColors.OnSurface,
fontSize = 14.sp
)
Text("Edit Kategori", color = Color.White)
}
},
onClick = {
@ -166,15 +206,19 @@ fun CategoryCard(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = Color(0xFFEF4444),
modifier = Modifier.size(20.dp)
tint = Constants.AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = Constants.AppColors.OnSurface,
fontSize = 14.sp
)
Text("Pindah ke Sampah", color = Color.White)
}
},
onClick = {
@ -185,6 +229,40 @@ fun CategoryCard(
}
}
}
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
// 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)
}
}
)

View File

@ -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
// Footer: Timestamp
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
dateFormat.format(Date(note.timestamp)),
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF64748B)
color = Constants.AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
}
}
}
}