Mengubah Warna dan menyesuaikan UI/UX Halaman AI Helper
This commit is contained in:
parent
0f0ac6b8f3
commit
80774b58ea
@ -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")
|
||||
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user