Menambahkan fitur pin dikategori dan catatan

This commit is contained in:
202310715082 FAZRI ABDURRAHMAN 2025-12-23 00:16:03 +07:00
parent 3692a291c7
commit 85a2c65017
12 changed files with 648 additions and 553 deletions

View File

@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-12-18T02:27:11.898714800Z">
<DropdownSelection timestamp="2025-12-18T06:53:17.556062600Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Fazri Abdurrahman\.android\avd\Medium_Tablet.avd" />
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Fazri Abdurrahman\.android\avd\Medium_Phone.avd" />
</handle>
</Target>
</DropdownSelection>

View File

@ -16,8 +16,6 @@ android {
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
@ -33,65 +31,20 @@ android {
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended-android:1.6.7")
implementation("com.google.android.material:material:1.9.0")
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)
implementation(libs.androidx.glance)
implementation(libs.androidx.animation)
implementation(libs.androidx.ui.graphics)
// Untuk integrasi Gemini AI (optional - uncomment jika sudah ada API key)
// implementation("com.google.ai.client.generativeai:generativeai:0.1.2")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.02.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
// File picker
implementation("androidx.activity:activity-compose:1.8.2")
// PDF Parser (ONLY THIS ONE!)
implementation("com.tom-roush:pdfbox-android:2.0.27.0")
// File operations
implementation("androidx.documentfile:documentfile:1.0.1")
}
android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
@ -106,4 +59,59 @@ android {
excludes += "/META-INF/*.kotlin_module"
}
}
}
dependencies {
// Core Android
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
// Compose BOM
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended-android:1.6.7")
// Material Design
implementation("com.google.android.material:material:1.9.0")
// DataStore
implementation("androidx.datastore:datastore-preferences:1.0.0")
// Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
// Gemini AI
implementation("com.google.ai.client.generativeai:generativeai:0.9.0")
// Version Catalog (libs)
implementation(libs.androidx.ui.text)
implementation(libs.androidx.material3)
implementation(libs.androidx.animation.core)
implementation(libs.androidx.glance)
implementation(libs.androidx.animation)
implementation(libs.androidx.ui.graphics)
// File operations
implementation("androidx.documentfile:documentfile:1.0.1")
// PDF Parser
implementation("com.tom-roush:pdfbox-android:2.0.27.0")
// FIREBASE SUDAH DIHAPUS - baris ini yang menyebabkan error
// implementation(libs.firebase.firestore.ktx)
// Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.02.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
// Debug
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}

View File

@ -44,11 +44,11 @@ class MainActivity : ComponentActivity() {
colorScheme = darkColorScheme(
primary = AppColors.Primary,
onPrimary = Color.White,
primaryContainer = AppColors.PrimaryContainer,
primaryContainer = AppColors.Primary.copy(alpha = 0.3f),
onPrimaryContainer = Color.White,
secondary = AppColors.Secondary,
onSecondary = Color.White,
secondaryContainer = AppColors.SecondaryVariant,
secondaryContainer = AppColors.Secondary.copy(alpha = 0.3f),
onSecondaryContainer = Color.White,
background = AppColors.Background,
onBackground = AppColors.OnBackground,
@ -90,15 +90,6 @@ class MainActivity : ComponentActivity() {
}
}
fun sortCategories(categories: List<Category>): List<Category> {
return categories
.filter { !it.isDeleted }
.sortedWith(
compareByDescending<Category> { it.isPinned } // Pinned dulu
.thenByDescending { it.timestamp } // Lalu timestamp
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NotesApp() {
@ -106,6 +97,7 @@ fun NotesApp() {
val dataStoreManager = remember { DataStoreManager(context) }
val scope = rememberCoroutineScope()
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
var categories by remember { mutableStateOf(listOf<Category>()) }
var notes by remember { mutableStateOf(listOf<Note>()) }
var selectedCategory by remember { mutableStateOf<Category?>(null) }
@ -120,10 +112,17 @@ fun NotesApp() {
var fullScreenNote by remember { mutableStateOf<Note?>(null) }
var isDarkTheme by remember { mutableStateOf(true) }
// Guard flags to prevent race conditions
var isDataLoaded by remember { mutableStateOf(false) }
// Load theme preference
fun sortCategories(categories: List<Category>): List<Category> {
return categories
.filter { !it.isDeleted }
.sortedWith(
compareByDescending<Category> { it.isPinned }
.thenByDescending { it.timestamp }
)
}
LaunchedEffect(Unit) {
dataStoreManager.themeFlow.collect { theme ->
isDarkTheme = theme == "dark"
@ -131,7 +130,6 @@ fun NotesApp() {
}
}
// Load categories ONCE
LaunchedEffect(Unit) {
dataStoreManager.categoriesFlow.collect { loadedCategories ->
if (!isDataLoaded) {
@ -141,18 +139,16 @@ fun NotesApp() {
}
}
// Load notes ONCE
LaunchedEffect(Unit) {
dataStoreManager.notesFlow.collect { loadedNotes ->
if (!isDataLoaded) {
android.util.Log.d("NotesApp", "Loading ${loadedNotes.size} notes")
notes = loadedNotes
isDataLoaded = true // Mark as loaded
isDataLoaded = true
}
}
}
// Save categories when changed
LaunchedEffect(categories) {
if (isDataLoaded && categories.isNotEmpty()) {
android.util.Log.d("NotesApp", "Saving ${categories.size} categories")
@ -162,7 +158,6 @@ fun NotesApp() {
}
}
// Save notes when changed
LaunchedEffect(notes) {
if (isDataLoaded && notes.isNotEmpty()) {
android.util.Log.d("NotesApp", "Saving ${notes.size} notes")
@ -172,7 +167,6 @@ fun NotesApp() {
}
}
// Save on lifecycle events
DisposableEffect(lifecycleOwner) {
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
if (event == androidx.lifecycle.Lifecycle.Event.ON_PAUSE ||
@ -319,7 +313,7 @@ fun NotesApp() {
) {
when (currentScreen) {
"main" -> MainScreen(
categories = categories.filter { !it.isDeleted },
categories = sortCategories(categories),
notes = notes,
selectedCategory = selectedCategory,
searchQuery = searchQuery,
@ -359,6 +353,12 @@ fun NotesApp() {
}
}
},
onCategoryPin = { category ->
categories = categories.map {
if (it.id == category.id) it.copy(isPinned = !it.isPinned)
else it
}
},
onNoteEdit = { note ->
editingNote = note
showNoteDialog = true
@ -439,7 +439,6 @@ fun NotesApp() {
}
}
// Dialogs
if (showCategoryDialog) {
CategoryDialog(
onDismiss = { showCategoryDialog = false },
@ -457,6 +456,7 @@ fun NotesApp() {
if (showNoteDialog && selectedCategory != null) {
NoteDialog(
note = editingNote,
categoryId = selectedCategory!!.id,
onDismiss = {
showNoteDialog = false
editingNote = null
@ -498,7 +498,6 @@ fun NotesApp() {
}
}
// Drawer with Animation
AnimatedVisibility(
visible = drawerState,
enter = fadeIn() + slideInHorizontally(
@ -530,4 +529,6 @@ fun NotesApp() {
)
}
}
}
}
private fun AppColors.setTheme(darkTheme: Boolean) {}

View File

@ -39,7 +39,7 @@ fun DrawerMenu(
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.Overlay)
.background(Color.Black.copy(alpha = 0.5f))
.clickable(
onClick = onDismiss,
indication = null,
@ -99,7 +99,7 @@ fun DrawerMenu(
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
Text(
Constants.APP_NAME,
"AI Notes",
style = MaterialTheme.typography.headlineMedium,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
@ -179,7 +179,7 @@ fun DrawerMenu(
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Version ${Constants.APP_VERSION}",
"Version 1.0.0",
style = MaterialTheme.typography.bodySmall,
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp

View File

@ -1,157 +1,168 @@
// File: presentation/dialogs/CategoryDialog.kt
package com.example.notesai.presentation.dialogs
package com.example.notesai.presentation.dialogs
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.Check
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.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.util.AppColors
import com.example.notesai.util.Constants
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.Check
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.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.util.AppColors
import com.example.notesai.util.Constants
@Composable
fun CategoryDialog(
onDismiss: () -> Unit,
onSave: (String, Long, Long) -> Unit
) {
var name by remember { mutableStateOf("") }
var selectedGradient by remember { mutableStateOf(0) }
@Composable
fun CategoryDialog(
onDismiss: () -> Unit,
onSave: (String, Long, Long) -> Unit
) {
var name by remember { mutableStateOf("") }
var selectedGradient by remember { mutableStateOf(0) }
AlertDialog(
onDismissRequest = onDismiss,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp),
title = {
Text(
"Buat Kategori Baru",
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
// Input Nama
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = {
Text(
"Nama Kategori",
color = AppColors.OnSurfaceVariant
)
},
placeholder = {
Text(
"Contoh: Pekerjaan, Personal",
color = AppColors.OnSurfaceTertiary,
fontSize = 14.sp
)
},
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface,
focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant,
cursorColor = AppColors.Primary,
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent
),
shape = RoundedCornerShape(12.dp),
singleLine = true
)
// Gradient Selector
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
AlertDialog(
onDismissRequest = onDismiss,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = {
Text(
"Kategori Baru",
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Name Input
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = {
Text(
"Pilih Warna:",
style = MaterialTheme.typography.bodyMedium,
color = AppColors.OnSurface,
fontWeight = FontWeight.SemiBold,
"Nama Kategori",
color = AppColors.OnSurfaceVariant
)
},
placeholder = {
Text(
"Contoh: Pekerjaan, Pribadi...",
color = AppColors.OnSurfaceTertiary,
fontSize = 14.sp
)
},
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface,
focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant,
cursorColor = AppColors.Primary,
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent
),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
singleLine = true
)
Constants.CategoryColors.chunked(4).forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
Spacer(modifier = Modifier.height(8.dp))
// Color picker title
Text(
"Pilih Warna:",
style = MaterialTheme.typography.bodyMedium,
color = AppColors.OnSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(8.dp))
// Color Grid
Constants.CategoryColors.chunked(4).forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
row.forEachIndexed { _, gradient ->
val globalIndex = Constants.CategoryColors.indexOf(gradient)
val isSelected = selectedGradient == globalIndex
Box(
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.clip(RoundedCornerShape(Constants.Radius.Medium.dp))
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(gradient.first),
Color(gradient.second)
)
)
)
.clickable { selectedGradient = globalIndex },
contentAlignment = Alignment.Center
) {
row.forEach { gradient ->
val globalIndex = Constants.CategoryColors.indexOf(gradient)
val isSelected = selectedGradient == globalIndex
// Scale animation
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.1f else 1f,
// Checkmark with animation
androidx.compose.animation.AnimatedVisibility(
visible = isSelected,
enter = scaleIn(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
),
label = "scale"
)
) + fadeIn(),
exit = scaleOut(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
) + fadeOut()
) {
Icon(
Icons.Default.Check,
contentDescription = "Selected",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
Box(
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.clip(RoundedCornerShape(12.dp))
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(gradient.first),
Color(gradient.second)
)
)
)
.clickable { selectedGradient = globalIndex },
contentAlignment = Alignment.Center
) {
// Check icon dengan animation
this@Row.AnimatedVisibility(
visible = isSelected,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
Surface(
color = Color.White.copy(alpha = 0.9f),
shape = RoundedCornerShape(8.dp)
) {
Icon(
Icons.Default.Check,
contentDescription = null,
tint = Color(gradient.first),
modifier = Modifier
.padding(6.dp)
.size(20.dp)
)
}
}
}
}
}
}
}
Spacer(modifier = Modifier.height(8.dp))
}
},
confirmButton = {
}
},
confirmButton = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Cancel button
TextButton(
onClick = onDismiss,
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp)
) {
Text(
"Batal",
color = AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
}
// Save button
Button(
onClick = {
if (name.isNotBlank()) {
@ -164,29 +175,17 @@
containerColor = AppColors.Primary,
disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f)
),
shape = RoundedCornerShape(12.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp)
) {
Text(
"Simpan",
"Buat",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 15.sp
)
}
},
dismissButton = {
TextButton(
onClick = onDismiss,
shape = RoundedCornerShape(12.dp),
modifier = Modifier.height(48.dp)
) {
Text(
"Batal",
color = AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
}
}
)
}
}
)
}

View File

@ -17,10 +17,11 @@ import com.example.notesai.util.Constants
@Composable
fun NoteDialog(
note: Note?,
categoryId: String, // Parameter untuk kategori ID
note: Note? = null, // Null jika buat baru, isi jika edit
onDismiss: () -> Unit,
onSave: (String, String) -> Unit,
onDelete: (() -> Unit)?
onDelete: (() -> Unit)? = null
) {
var title by remember { mutableStateOf(note?.title ?: "") }
var description by remember { mutableStateOf(note?.description ?: "") }
@ -31,7 +32,7 @@ fun NoteDialog(
AlertDialog(
onDismissRequest = { showDeleteConfirm = false },
containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = {
Text(
"Hapus Catatan?",
@ -54,7 +55,7 @@ fun NoteDialog(
colors = ButtonDefaults.buttonColors(
containerColor = AppColors.Error
),
shape = RoundedCornerShape(12.dp)
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
) {
Text("Hapus", color = Color.White, fontWeight = FontWeight.Bold)
}
@ -62,7 +63,7 @@ fun NoteDialog(
dismissButton = {
TextButton(
onClick = { showDeleteConfirm = false },
shape = RoundedCornerShape(12.dp)
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
) {
Text("Batal", color = AppColors.OnSurfaceVariant)
}
@ -73,7 +74,7 @@ fun NoteDialog(
AlertDialog(
onDismissRequest = onDismiss,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = {
Text(
if (note == null) "Catatan Baru" else "Edit Catatan",
@ -113,7 +114,7 @@ fun NoteDialog(
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent
),
shape = RoundedCornerShape(12.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
singleLine = true
)
@ -146,7 +147,7 @@ fun NoteDialog(
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent
),
shape = RoundedCornerShape(12.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
maxLines = 8
)
}
@ -174,7 +175,7 @@ fun NoteDialog(
// Cancel button
TextButton(
onClick = onDismiss,
shape = RoundedCornerShape(12.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp)
) {
Text(
@ -196,7 +197,7 @@ fun NoteDialog(
containerColor = AppColors.Primary,
disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f)
),
shape = RoundedCornerShape(12.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp)
) {
Text(

View File

@ -72,7 +72,7 @@ fun ChatHistoryDrawer(
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.Overlay)
.background(Color.Black.copy(alpha = 0.5f))
.clickable(
onClick = onDismiss,
indication = null,
@ -883,6 +883,18 @@ private fun EmptyHistoryState(
Spacer(modifier = Modifier.height(6.dp))
Text(
if (searchQuery.isNotEmpty())
"Coba kata kunci lain"
else
"Mulai chat dengan AI",
style = MaterialTheme.typography.bodySmall,
color = AppColors.OnSurfaceTertiary,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(6.dp))
Text(
if (searchQuery.isNotEmpty())
"Coba kata kunci lain"

View File

@ -28,6 +28,7 @@ fun MainScreen(
onPinToggle: (Note) -> Unit,
onCategoryDelete: (Category) -> Unit,
onCategoryEdit: (Category, String, Long, Long) -> Unit,
onCategoryPin: (Category) -> Unit, // NEW: Pin category callback
onNoteEdit: (Note) -> Unit = {},
onNoteDelete: (Note) -> Unit = {}
) {
@ -63,7 +64,7 @@ fun MainScreen(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 100.dp // Extra space untuk floating bottom bar
bottom = 100.dp
),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalItemSpacing = 12.dp,
@ -81,7 +82,8 @@ fun MainScreen(
onDelete = { onCategoryDelete(category) },
onEdit = { name, gradientStart, gradientEnd ->
onCategoryEdit(category, name, gradientStart, gradientEnd)
}
},
onPin = { onCategoryPin(category) } // NEW: Pass pin callback
)
}
}
@ -112,7 +114,7 @@ fun MainScreen(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 100.dp // Extra space untuk floating bottom bar
bottom = 100.dp
),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalItemSpacing = 12.dp,

View File

@ -31,7 +31,8 @@ fun CategoryCard(
noteCount: Int,
onClick: () -> Unit,
onDelete: () -> Unit = {},
onEdit: (String, Long, Long) -> Unit = { _, _, _ -> }
onEdit: (String, Long, Long) -> Unit = { _, _, _ -> },
onPin: () -> Unit = {} // NEW: Pin callback
) {
var showDeleteConfirm by remember { mutableStateOf(false) }
var showEditDialog by remember { mutableStateOf(false) }
@ -159,74 +160,126 @@ fun CategoryCard(
)
}
// Menu Button
Box {
IconButton(
onClick = { showMenu = true },
modifier = Modifier.size(32.dp)
// NEW: Pin Indicator & Menu Button
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Pin indicator (only show if pinned)
AnimatedVisibility(
visible = category.isPinned,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = AppColors.OnSurfaceVariant,
Icons.Default.PushPin,
contentDescription = "Pinned",
tint = AppColors.Warning,
modifier = Modifier.size(20.dp)
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Kategori",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showEditDialog = true
}
)
// Menu Button
Box {
IconButton(
onClick = { showMenu = true },
modifier = Modifier.size(32.dp)
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = AppColors.OnSurface,
fontSize = 14.sp
)
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated)
) {
// NEW: Pin/Unpin Menu Item
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.PushPin,
contentDescription = null,
tint = if (category.isPinned) AppColors.Warning else AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp)
)
Text(
if (category.isPinned) "Lepas Pin" else "Pin Kategori",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
onPin()
showMenu = false
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
)
)
HorizontalDivider(color = AppColors.Divider)
// Edit Menu Item
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Kategori",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showEditDialog = true
}
)
HorizontalDivider(color = AppColors.Divider)
// Delete Menu Item
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
)
}
}
}
}

View File

@ -1,6 +1,7 @@
// File: presentation/screens/main/components/NoteCard.kt
package com.example.notesai.presentation.screens.main.components
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
@ -9,11 +10,11 @@ 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.StarBorder
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
@ -50,6 +51,23 @@ fun NoteCard(
label = "scale"
)
// Pin icon rotation animation
val pinRotation by animateFloatAsState(
targetValue = if (note.isPinned) 0f else 45f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
),
label = "rotation"
)
// Elevation animation for pinned state
val elevation by animateDpAsState(
targetValue = if (note.isPinned) Constants.Elevation.Medium.dp else Constants.Elevation.Small.dp,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "elevation"
)
// Delete Confirmation Dialog
if (showDeleteConfirm) {
AlertDialog(
@ -58,7 +76,8 @@ fun NoteCard(
Icon(
Icons.Default.DeleteForever,
contentDescription = null,
tint = AppColors.Error
tint = AppColors.Error,
modifier = Modifier.size(32.dp)
)
},
title = {
@ -104,10 +123,13 @@ fun NoteCard(
.combinedClickable(onClick = onClick),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
colors = CardDefaults.cardColors(
containerColor = AppColors.SurfaceVariant
containerColor = if (note.isPinned)
AppColors.SurfaceVariant.copy(alpha = 0.95f)
else
AppColors.SurfaceVariant
),
elevation = CardDefaults.cardElevation(
defaultElevation = Constants.Elevation.Small.dp
defaultElevation = elevation
)
) {
Column(
@ -115,116 +137,166 @@ fun NoteCard(
.fillMaxWidth()
.padding(Constants.Spacing.Large.dp)
) {
// Header: Title + Actions (Vertical)
// Header: Title + Menu
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
// Title - takes most space
Text(
note.title,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = AppColors.OnBackground,
// Title with pin badge
Row(
modifier = Modifier.weight(1f),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
fontSize = 18.sp
)
// Vertical Actions Stack
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(0.dp)
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Menu Button
Box {
IconButton(
onClick = { showMenu = true },
modifier = Modifier.size(28.dp)
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp)
)
}
Text(
note.title,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = AppColors.OnBackground,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
fontSize = 18.sp,
modifier = Modifier.weight(1f, fill = false)
)
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated)
// Pin Badge next to title
AnimatedVisibility(
visible = note.isPinned,
enter = scaleIn(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) + fadeIn(),
exit = scaleOut(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) + fadeOut()
) {
Surface(
color = AppColors.Warning.copy(alpha = 0.2f),
shape = RoundedCornerShape(Constants.Radius.Small.dp)
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Catatan",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
onEdit()
}
)
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.padding(horizontal = 6.dp, vertical = 3.dp)
) {
Icon(
Icons.Filled.PushPin,
contentDescription = "Disematkan",
tint = AppColors.Warning,
modifier = Modifier
.size(12.dp)
.rotate(45f)
)
Text(
"Pin",
fontSize = 10.sp,
fontWeight = FontWeight.Bold,
color = AppColors.Warning
)
}
}
}
}
// Pin Button
// Menu Button
Box {
IconButton(
onClick = onPinClick,
modifier = Modifier.size(28.dp)
onClick = { showMenu = true },
modifier = Modifier.size(32.dp)
) {
Icon(
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
contentDescription = "Pin",
tint = if (note.isPinned) AppColors.Warning else AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp)
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated)
) {
// Pin/Unpin option
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Filled.PushPin,
contentDescription = null,
tint = if (note.isPinned) AppColors.Warning else AppColors.Primary,
modifier = Modifier
.size(18.dp)
.rotate(pinRotation)
)
Text(
if (note.isPinned) "Lepas Sematan" else "Sematkan",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
onPinClick()
}
)
HorizontalDivider(color = AppColors.Divider)
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Catatan",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
onEdit()
}
)
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
)
}
}
}
// Deskripsi Preview
// Description Preview
if (note.description.isNotEmpty()) {
Spacer(modifier = Modifier.height(12.dp))
@ -238,7 +310,6 @@ fun NoteCard(
fontSize = 14.sp
)
} else {
// Tampilkan placeholder jika deskripsi kosong
Spacer(modifier = Modifier.height(12.dp))
Text(
@ -272,6 +343,22 @@ fun NoteCard(
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
// Pin indicator icon in footer
AnimatedVisibility(
visible = note.isPinned,
enter = fadeIn() + expandHorizontally(),
exit = fadeOut() + shrinkHorizontally()
) {
Icon(
Icons.Filled.PushPin,
contentDescription = "Disematkan",
tint = AppColors.Warning.copy(alpha = 0.6f),
modifier = Modifier
.size(14.dp)
.rotate(45f)
)
}
}
}
}

View File

@ -0,0 +1,3 @@
import com.example.notesai.util.AppColors
annotation class AppColors

View File

@ -1,193 +1,122 @@
package com.example.notesai.util
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
object AppColors {
// Primary Colors
val Primary = Color(0xFF6C63FF)
val Secondary = Color(0xFF03DAC6)
val Accent = Color(0xFFFF6B9D)
// Background Colors
val Background = Color(0xFF121212)
val Surface = Color(0xFF1E1E1E)
val SurfaceVariant = Color(0xFF2A2A2A)
val SurfaceElevated = Color(0xFF252525)
// Text Colors
val OnBackground = Color(0xFFE1E1E1)
val OnSurface = Color(0xFFCCCCCC)
val OnSurfaceVariant = Color(0xFF9E9E9E)
val OnSurfaceTertiary = Color(0xFF757575)
// Utility Colors
val Error = Color(0xFFCF6679)
val Warning = Color(0xFFFFB74D)
val Success = Color(0xFF81C784)
val Info = Color(0xFF64B5F6)
// Border & Divider
val Border = Color(0xFF3A3A3A)
val Divider = Color(0xFF2E2E2E)
}
object Constants {
// App Info
const val APP_NAME = "NotesAI"
const val APP_VERSION = "1.1.0"
// DataStore
const val DATASTORE_NAME = "notes_prefs"
const val DEBOUNCE_DELAY = 500L
// UI Constants
const val MAX_NOTE_PREVIEW_LINES = 4
const val MAX_CHAT_PREVIEW_LINES = 2
const val GRID_COLUMNS = 2
// DARK THEME COLORS
object DarkColors {
val Background = Color(0xFF0A0A0A)
val Surface = Color(0xFF141414)
val SurfaceVariant = Color(0xFF1E1E1E)
val SurfaceElevated = Color(0xFF252525)
val Primary = Color(0xFF3B82F6)
val PrimaryVariant = Color(0xFF60A5FA)
val PrimaryContainer = Color(0xFF1E3A8A)
val Secondary = Color(0xFF8B5CF6)
val SecondaryVariant = Color(0xFFA78BFA)
val OnBackground = Color(0xFFFFFFFF)
val OnSurface = Color(0xFFE5E5E5)
val OnSurfaceVariant = Color(0xFF9CA3AF)
val OnSurfaceTertiary = Color(0xFF6B7280)
val Success = Color(0xFF10B981)
val Error = Color(0xFFEF4444)
val Warning = Color(0xFFFBBF24)
val Info = Color(0xFF3B82F6)
val Border = Color(0xFF2A2A2A)
val Divider = Color(0xFF1F1F1F)
val Overlay = Color(0xFF000000).copy(alpha = 0.5f)
val Shadow = Color(0xFF000000).copy(alpha = 0.3f)
}
// LIGHT THEME COLORS
object LightColors {
val Background = Color(0xFFF8F9FA)
val Surface = Color(0xFFFFFFFF)
val SurfaceVariant = Color(0xFFF1F3F5)
val SurfaceElevated = Color(0xFFFFFFFF)
val Primary = Color(0xFF3B82F6)
val PrimaryVariant = Color(0xFF2563EB)
val PrimaryContainer = Color(0xFFDCEEFF)
val Secondary = Color(0xFF8B5CF6)
val SecondaryVariant = Color(0xFF7C3AED)
val OnBackground = Color(0xFF1F2937)
val OnSurface = Color(0xFF374151)
val OnSurfaceVariant = Color(0xFF6B7280)
val OnSurfaceTertiary = Color(0xFF9CA3AF)
val Success = Color(0xFF10B981)
val Error = Color(0xFFEF4444)
val Warning = Color(0xFFA16207)
val Info = Color(0xFF3B82F6)
val Border = Color(0xFFE5E7EB)
val Divider = Color(0xFFF3F4F6)
val Overlay = Color(0xFF000000).copy(alpha = 0.3f)
val Shadow = Color(0xFF000000).copy(alpha = 0.1f)
}
// Category Colors - Same for both themes
val CategoryColors = listOf(
Pair(0xFF3B82F6L, 0xFF60A5FAL), // Blue
Pair(0xFF8B5CF6L, 0xFFA78BFAL), // Purple
Pair(0xFF10B981L, 0xFF34D399L), // Green
Pair(0xFFF59E0BL, 0xFFFBBF24L), // Amber
Pair(0xFFEF4444L, 0xFFF87171L), // Red
Pair(0xFF06B6D4L, 0xFF22D3EEL), // Cyan
Pair(0xFFEC4899L, 0xFFF472B6L), // Pink
Pair(0xFF6366F1L, 0xFF818CF8L) // Indigo
)
// Animation Durations
const val ANIMATION_DURATION_SHORT = 150
const val ANIMATION_DURATION_MEDIUM = 300
const val ANIMATION_DURATION_LONG = 500
const val FADE_IN_DURATION = 200
const val FADE_OUT_DURATION = 200
// Spacing System
// Spacing values
object Spacing {
const val ExtraSmall = 4
const val Small = 8
const val Medium = 16
const val Large = 24
const val ExtraLarge = 32
const val XXLarge = 48
const val Medium = 12
const val Large = 16
const val ExtraLarge = 24
const val ExtraExtraLarge = 32
}
// Corner Radius
// Border Radius values
object Radius {
const val Small = 8
const val Medium = 12
const val Large = 16
const val ExtraLarge = 20
const val Round = 999
const val ExtraLarge = 24
const val ExtraExtraLarge = 32
}
// Elevation
// Elevation values
object Elevation {
const val None = 0
const val Small = 2
const val Medium = 4
const val Large = 8
const val ExtraLarge = 16
}
}
// REACTIVE APP COLORS - Using Compose State
object AppColors {
// Internal state
private var _isDark by mutableStateOf(true)
// Public setter
fun setTheme(isDark: Boolean) {
_isDark = isDark
const val ExtraLarge = 12
}
// All colors are now reactive via mutableStateOf
val Background: Color
get() = if (_isDark) Constants.DarkColors.Background else Constants.LightColors.Background
// Reference to AppColors for compatibility
val AppColors = com.example.notesai.util.AppColors
val Surface: Color
get() = if (_isDark) Constants.DarkColors.Surface else Constants.LightColors.Surface
// Category gradient colors
val CategoryColors = listOf(
// Purple gradients
0xFF6750A4L to 0xFF7E57C2L,
0xFF9C27B0L to 0xFFE91E63L,
val SurfaceVariant: Color
get() = if (_isDark) Constants.DarkColors.SurfaceVariant else Constants.LightColors.SurfaceVariant
// Blue gradients
0xFF2196F3L to 0xFF03A9F4L,
0xFF1976D2L to 0xFF4FC3F7L,
val SurfaceElevated: Color
get() = if (_isDark) Constants.DarkColors.SurfaceElevated else Constants.LightColors.SurfaceElevated
// Green gradients
0xFF4CAF50L to 0xFF8BC34AL,
0xFF009688L to 0xFF00BCD4L,
val Primary: Color
get() = if (_isDark) Constants.DarkColors.Primary else Constants.LightColors.Primary
// Orange gradients
0xFFFF9800L to 0xFFFFB74DL,
0xFFFF5722L to 0xFFFF7043L,
val PrimaryVariant: Color
get() = if (_isDark) Constants.DarkColors.PrimaryVariant else Constants.LightColors.PrimaryVariant
// Red gradients
0xFFF44336L to 0xFFE91E63L,
0xFFD32F2FL to 0xFFFF5252L,
val PrimaryContainer: Color
get() = if (_isDark) Constants.DarkColors.PrimaryContainer else Constants.LightColors.PrimaryContainer
// Teal gradients
0xFF009688L to 0xFF26A69AL,
0xFF00897BL to 0xFF4DB6ACL,
val Secondary: Color
get() = if (_isDark) Constants.DarkColors.Secondary else Constants.LightColors.Secondary
// Indigo gradients
0xFF3F51B5L to 0xFF5C6BC0L,
0xFF303F9FL to 0xFF7986CBL,
val SecondaryVariant: Color
get() = if (_isDark) Constants.DarkColors.SecondaryVariant else Constants.LightColors.SecondaryVariant
// Amber gradients
0xFFFFC107L to 0xFFFFD54FL,
0xFFFFB300L to 0xFFFFCA28L,
val OnBackground: Color
get() = if (_isDark) Constants.DarkColors.OnBackground else Constants.LightColors.OnBackground
// Pink gradients
0xFFE91E63L to 0xFFF06292L,
0xFFC2185BL to 0xFFEC407AL,
val OnSurface: Color
get() = if (_isDark) Constants.DarkColors.OnSurface else Constants.LightColors.OnSurface
// Cyan gradients
0xFF00BCD4L to 0xFF26C6DAL,
0xFF0097A7L to 0xFF00ACC1L,
val OnSurfaceVariant: Color
get() = if (_isDark) Constants.DarkColors.OnSurfaceVariant else Constants.LightColors.OnSurfaceVariant
// Deep Purple gradients
0xFF673AB7L to 0xFF9575CDL,
0xFF512DA8L to 0xFF7E57C2L,
val OnSurfaceTertiary: Color
get() = if (_isDark) Constants.DarkColors.OnSurfaceTertiary else Constants.LightColors.OnSurfaceTertiary
val Success: Color
get() = if (_isDark) Constants.DarkColors.Success else Constants.LightColors.Success
val Error: Color
get() = if (_isDark) Constants.DarkColors.Error else Constants.LightColors.Error
val Warning: Color
get() = if (_isDark) Constants.DarkColors.Warning else Constants.LightColors.Warning
val Info: Color
get() = if (_isDark) Constants.DarkColors.Info else Constants.LightColors.Info
val Border: Color
get() = if (_isDark) Constants.DarkColors.Border else Constants.LightColors.Border
val Divider: Color
get() = if (_isDark) Constants.DarkColors.Divider else Constants.LightColors.Divider
val Overlay: Color
get() = if (_isDark) Constants.DarkColors.Overlay else Constants.LightColors.Overlay
val Shadow: Color
get() = if (_isDark) Constants.DarkColors.Shadow else Constants.LightColors.Shadow
// Lime gradients
0xFFCDDC39L to 0xFFD4E157L,
0xFFAFB42BL to 0xFFC0CA33L
)
}