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> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <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"> <Target type="DEFAULT_BOOT">
<handle> <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> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -16,8 +16,6 @@ android {
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true
@ -33,65 +31,20 @@ android {
) )
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
buildFeatures { buildFeatures {
compose = true 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 { packaging {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
@ -107,3 +60,58 @@ android {
} }
} }
} }
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( colorScheme = darkColorScheme(
primary = AppColors.Primary, primary = AppColors.Primary,
onPrimary = Color.White, onPrimary = Color.White,
primaryContainer = AppColors.PrimaryContainer, primaryContainer = AppColors.Primary.copy(alpha = 0.3f),
onPrimaryContainer = Color.White, onPrimaryContainer = Color.White,
secondary = AppColors.Secondary, secondary = AppColors.Secondary,
onSecondary = Color.White, onSecondary = Color.White,
secondaryContainer = AppColors.SecondaryVariant, secondaryContainer = AppColors.Secondary.copy(alpha = 0.3f),
onSecondaryContainer = Color.White, onSecondaryContainer = Color.White,
background = AppColors.Background, background = AppColors.Background,
onBackground = AppColors.OnBackground, 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun NotesApp() { fun NotesApp() {
@ -106,6 +97,7 @@ fun NotesApp() {
val dataStoreManager = remember { DataStoreManager(context) } val dataStoreManager = remember { DataStoreManager(context) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
var categories by remember { mutableStateOf(listOf<Category>()) } var categories by remember { mutableStateOf(listOf<Category>()) }
var notes by remember { mutableStateOf(listOf<Note>()) } var notes by remember { mutableStateOf(listOf<Note>()) }
var selectedCategory by remember { mutableStateOf<Category?>(null) } var selectedCategory by remember { mutableStateOf<Category?>(null) }
@ -120,10 +112,17 @@ fun NotesApp() {
var fullScreenNote by remember { mutableStateOf<Note?>(null) } var fullScreenNote by remember { mutableStateOf<Note?>(null) }
var isDarkTheme by remember { mutableStateOf(true) } var isDarkTheme by remember { mutableStateOf(true) }
// Guard flags to prevent race conditions
var isDataLoaded by remember { mutableStateOf(false) } 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) { LaunchedEffect(Unit) {
dataStoreManager.themeFlow.collect { theme -> dataStoreManager.themeFlow.collect { theme ->
isDarkTheme = theme == "dark" isDarkTheme = theme == "dark"
@ -131,7 +130,6 @@ fun NotesApp() {
} }
} }
// Load categories ONCE
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
dataStoreManager.categoriesFlow.collect { loadedCategories -> dataStoreManager.categoriesFlow.collect { loadedCategories ->
if (!isDataLoaded) { if (!isDataLoaded) {
@ -141,18 +139,16 @@ fun NotesApp() {
} }
} }
// Load notes ONCE
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
dataStoreManager.notesFlow.collect { loadedNotes -> dataStoreManager.notesFlow.collect { loadedNotes ->
if (!isDataLoaded) { if (!isDataLoaded) {
android.util.Log.d("NotesApp", "Loading ${loadedNotes.size} notes") android.util.Log.d("NotesApp", "Loading ${loadedNotes.size} notes")
notes = loadedNotes notes = loadedNotes
isDataLoaded = true // Mark as loaded isDataLoaded = true
} }
} }
} }
// Save categories when changed
LaunchedEffect(categories) { LaunchedEffect(categories) {
if (isDataLoaded && categories.isNotEmpty()) { if (isDataLoaded && categories.isNotEmpty()) {
android.util.Log.d("NotesApp", "Saving ${categories.size} categories") android.util.Log.d("NotesApp", "Saving ${categories.size} categories")
@ -162,7 +158,6 @@ fun NotesApp() {
} }
} }
// Save notes when changed
LaunchedEffect(notes) { LaunchedEffect(notes) {
if (isDataLoaded && notes.isNotEmpty()) { if (isDataLoaded && notes.isNotEmpty()) {
android.util.Log.d("NotesApp", "Saving ${notes.size} notes") android.util.Log.d("NotesApp", "Saving ${notes.size} notes")
@ -172,7 +167,6 @@ fun NotesApp() {
} }
} }
// Save on lifecycle events
DisposableEffect(lifecycleOwner) { DisposableEffect(lifecycleOwner) {
val observer = androidx.lifecycle.LifecycleEventObserver { _, event -> val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
if (event == androidx.lifecycle.Lifecycle.Event.ON_PAUSE || if (event == androidx.lifecycle.Lifecycle.Event.ON_PAUSE ||
@ -319,7 +313,7 @@ fun NotesApp() {
) { ) {
when (currentScreen) { when (currentScreen) {
"main" -> MainScreen( "main" -> MainScreen(
categories = categories.filter { !it.isDeleted }, categories = sortCategories(categories),
notes = notes, notes = notes,
selectedCategory = selectedCategory, selectedCategory = selectedCategory,
searchQuery = searchQuery, 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 -> onNoteEdit = { note ->
editingNote = note editingNote = note
showNoteDialog = true showNoteDialog = true
@ -439,7 +439,6 @@ fun NotesApp() {
} }
} }
// Dialogs
if (showCategoryDialog) { if (showCategoryDialog) {
CategoryDialog( CategoryDialog(
onDismiss = { showCategoryDialog = false }, onDismiss = { showCategoryDialog = false },
@ -457,6 +456,7 @@ fun NotesApp() {
if (showNoteDialog && selectedCategory != null) { if (showNoteDialog && selectedCategory != null) {
NoteDialog( NoteDialog(
note = editingNote, note = editingNote,
categoryId = selectedCategory!!.id,
onDismiss = { onDismiss = {
showNoteDialog = false showNoteDialog = false
editingNote = null editingNote = null
@ -498,7 +498,6 @@ fun NotesApp() {
} }
} }
// Drawer with Animation
AnimatedVisibility( AnimatedVisibility(
visible = drawerState, visible = drawerState,
enter = fadeIn() + slideInHorizontally( enter = fadeIn() + slideInHorizontally(
@ -531,3 +530,5 @@ fun NotesApp() {
} }
} }
} }
private fun AppColors.setTheme(darkTheme: Boolean) {}

View File

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

View File

@ -1,4 +1,3 @@
// 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.*
@ -33,10 +32,10 @@
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
containerColor = AppColors.Surface, containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = { title = {
Text( Text(
"Buat Kategori Baru", "Kategori Baru",
color = AppColors.OnBackground, color = AppColors.OnBackground,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 20.sp fontSize = 20.sp
@ -44,9 +43,9 @@
}, },
text = { text = {
Column( Column(
verticalArrangement = Arrangement.spacedBy(20.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
// Input Nama // Name Input
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
@ -58,7 +57,7 @@
}, },
placeholder = { placeholder = {
Text( Text(
"Contoh: Pekerjaan, Personal", "Contoh: Pekerjaan, Pribadi...",
color = AppColors.OnSurfaceTertiary, color = AppColors.OnSurfaceTertiary,
fontSize = 14.sp fontSize = 14.sp
) )
@ -73,14 +72,13 @@
focusedBorderColor = AppColors.Primary, focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent unfocusedBorderColor = Color.Transparent
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
singleLine = true singleLine = true
) )
// Gradient Selector Spacer(modifier = Modifier.height(8.dp))
Column(
verticalArrangement = Arrangement.spacedBy(12.dp) // Color picker title
) {
Text( Text(
"Pilih Warna:", "Pilih Warna:",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
@ -89,30 +87,23 @@
fontSize = 14.sp fontSize = 14.sp
) )
Spacer(modifier = Modifier.height(8.dp))
// Color Grid
Constants.CategoryColors.chunked(4).forEach { row -> Constants.CategoryColors.chunked(4).forEach { row ->
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
row.forEach { gradient -> row.forEachIndexed { _, gradient ->
val globalIndex = Constants.CategoryColors.indexOf(gradient) val globalIndex = Constants.CategoryColors.indexOf(gradient)
val isSelected = selectedGradient == globalIndex val isSelected = selectedGradient == globalIndex
// Scale animation
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.1f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
),
label = "scale"
)
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(Constants.Radius.Medium.dp))
.background( .background(
brush = Brush.linearGradient( brush = Brush.linearGradient(
colors = listOf( colors = listOf(
@ -124,34 +115,54 @@
.clickable { selectedGradient = globalIndex }, .clickable { selectedGradient = globalIndex },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// Check icon dengan animation // Checkmark with animation
this@Row.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = isSelected, visible = isSelected,
enter = scaleIn() + fadeIn(), enter = scaleIn(
exit = scaleOut() + fadeOut() animationSpec = spring(
) { dampingRatio = Spring.DampingRatioMediumBouncy,
Surface( stiffness = Spring.StiffnessMedium
color = Color.White.copy(alpha = 0.9f), )
shape = RoundedCornerShape(8.dp) ) + fadeIn(),
exit = scaleOut(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
) + fadeOut()
) { ) {
Icon( Icon(
Icons.Default.Check, Icons.Default.Check,
contentDescription = null, contentDescription = "Selected",
tint = Color(gradient.first), tint = Color.White,
modifier = Modifier modifier = Modifier.size(24.dp)
.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( Button(
onClick = { onClick = {
if (name.isNotBlank()) { if (name.isNotBlank()) {
@ -164,28 +175,16 @@
containerColor = AppColors.Primary, containerColor = AppColors.Primary,
disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f) disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f)
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp) modifier = Modifier.height(48.dp)
) { ) {
Text( Text(
"Simpan", "Buat",
color = Color.White, color = Color.White,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 15.sp 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 @Composable
fun NoteDialog( fun NoteDialog(
note: Note?, categoryId: String, // Parameter untuk kategori ID
note: Note? = null, // Null jika buat baru, isi jika edit
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSave: (String, String) -> Unit, onSave: (String, String) -> Unit,
onDelete: (() -> Unit)? onDelete: (() -> Unit)? = null
) { ) {
var title by remember { mutableStateOf(note?.title ?: "") } var title by remember { mutableStateOf(note?.title ?: "") }
var description by remember { mutableStateOf(note?.description ?: "") } var description by remember { mutableStateOf(note?.description ?: "") }
@ -31,7 +32,7 @@ fun NoteDialog(
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteConfirm = false }, onDismissRequest = { showDeleteConfirm = false },
containerColor = AppColors.Surface, containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = { title = {
Text( Text(
"Hapus Catatan?", "Hapus Catatan?",
@ -54,7 +55,7 @@ fun NoteDialog(
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = AppColors.Error containerColor = AppColors.Error
), ),
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(Constants.Radius.Medium.dp)
) { ) {
Text("Hapus", color = Color.White, fontWeight = FontWeight.Bold) Text("Hapus", color = Color.White, fontWeight = FontWeight.Bold)
} }
@ -62,7 +63,7 @@ fun NoteDialog(
dismissButton = { dismissButton = {
TextButton( TextButton(
onClick = { showDeleteConfirm = false }, onClick = { showDeleteConfirm = false },
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(Constants.Radius.Medium.dp)
) { ) {
Text("Batal", color = AppColors.OnSurfaceVariant) Text("Batal", color = AppColors.OnSurfaceVariant)
} }
@ -73,7 +74,7 @@ fun NoteDialog(
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
containerColor = AppColors.Surface, containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = { title = {
Text( Text(
if (note == null) "Catatan Baru" else "Edit Catatan", if (note == null) "Catatan Baru" else "Edit Catatan",
@ -113,7 +114,7 @@ fun NoteDialog(
focusedBorderColor = AppColors.Primary, focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent unfocusedBorderColor = Color.Transparent
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
singleLine = true singleLine = true
) )
@ -146,7 +147,7 @@ fun NoteDialog(
focusedBorderColor = AppColors.Primary, focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent unfocusedBorderColor = Color.Transparent
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
maxLines = 8 maxLines = 8
) )
} }
@ -174,7 +175,7 @@ fun NoteDialog(
// Cancel button // Cancel button
TextButton( TextButton(
onClick = onDismiss, onClick = onDismiss,
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp) modifier = Modifier.height(48.dp)
) { ) {
Text( Text(
@ -196,7 +197,7 @@ fun NoteDialog(
containerColor = AppColors.Primary, containerColor = AppColors.Primary,
disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f) disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f)
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
modifier = Modifier.height(48.dp) modifier = Modifier.height(48.dp)
) { ) {
Text( Text(

View File

@ -72,7 +72,7 @@ fun ChatHistoryDrawer(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(AppColors.Overlay) .background(Color.Black.copy(alpha = 0.5f))
.clickable( .clickable(
onClick = onDismiss, onClick = onDismiss,
indication = null, indication = null,
@ -883,6 +883,18 @@ private fun EmptyHistoryState(
Spacer(modifier = Modifier.height(6.dp)) 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( Text(
if (searchQuery.isNotEmpty()) if (searchQuery.isNotEmpty())
"Coba kata kunci lain" "Coba kata kunci lain"

View File

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

View File

@ -31,7 +31,8 @@ fun CategoryCard(
noteCount: Int, noteCount: Int,
onClick: () -> Unit, onClick: () -> Unit,
onDelete: () -> 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 showDeleteConfirm by remember { mutableStateOf(false) }
var showEditDialog by remember { mutableStateOf(false) } var showEditDialog by remember { mutableStateOf(false) }
@ -159,6 +160,25 @@ fun CategoryCard(
) )
} }
// 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.PushPin,
contentDescription = "Pinned",
tint = AppColors.Warning,
modifier = Modifier.size(20.dp)
)
}
// Menu Button // Menu Button
Box { Box {
IconButton( IconButton(
@ -178,6 +198,35 @@ fun CategoryCard(
onDismissRequest = { showMenu = false }, onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated) 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
}
)
HorizontalDivider(color = AppColors.Divider)
// Edit Menu Item
DropdownMenuItem( DropdownMenuItem(
text = { text = {
Row( Row(
@ -203,6 +252,9 @@ fun CategoryCard(
} }
) )
HorizontalDivider(color = AppColors.Divider)
// Delete Menu Item
DropdownMenuItem( DropdownMenuItem(
text = { text = {
Row( Row(
@ -230,6 +282,7 @@ fun CategoryCard(
} }
} }
} }
}
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp)) Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))

View File

@ -1,6 +1,7 @@
// File: presentation/screens/main/components/NoteCard.kt // File: presentation/screens/main/components/NoteCard.kt
package com.example.notesai.presentation.screens.main.components package com.example.notesai.presentation.screens.main.components
import androidx.compose.animation.*
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -9,11 +10,11 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -50,6 +51,23 @@ fun NoteCard(
label = "scale" 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 // Delete Confirmation Dialog
if (showDeleteConfirm) { if (showDeleteConfirm) {
AlertDialog( AlertDialog(
@ -58,7 +76,8 @@ fun NoteCard(
Icon( Icon(
Icons.Default.DeleteForever, Icons.Default.DeleteForever,
contentDescription = null, contentDescription = null,
tint = AppColors.Error tint = AppColors.Error,
modifier = Modifier.size(32.dp)
) )
}, },
title = { title = {
@ -104,10 +123,13 @@ fun NoteCard(
.combinedClickable(onClick = onClick), .combinedClickable(onClick = onClick),
shape = RoundedCornerShape(Constants.Radius.Large.dp), shape = RoundedCornerShape(Constants.Radius.Large.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = AppColors.SurfaceVariant containerColor = if (note.isPinned)
AppColors.SurfaceVariant.copy(alpha = 0.95f)
else
AppColors.SurfaceVariant
), ),
elevation = CardDefaults.cardElevation( elevation = CardDefaults.cardElevation(
defaultElevation = Constants.Elevation.Small.dp defaultElevation = elevation
) )
) { ) {
Column( Column(
@ -115,40 +137,74 @@ fun NoteCard(
.fillMaxWidth() .fillMaxWidth()
.padding(Constants.Spacing.Large.dp) .padding(Constants.Spacing.Large.dp)
) { ) {
// Header: Title + Actions (Vertical) // Header: Title + Menu
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
// Title - takes most space // Title with pin badge
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text( Text(
note.title, note.title,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AppColors.OnBackground, color = AppColors.OnBackground,
modifier = Modifier.weight(1f),
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
fontSize = 18.sp fontSize = 18.sp,
modifier = Modifier.weight(1f, fill = false)
) )
// Vertical Actions Stack // Pin Badge next to title
Column( AnimatedVisibility(
horizontalAlignment = Alignment.End, visible = note.isPinned,
verticalArrangement = Arrangement.spacedBy(0.dp) 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)
) {
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
)
}
}
}
}
// Menu Button // Menu Button
Box { Box {
IconButton( IconButton(
onClick = { showMenu = true }, onClick = { showMenu = true },
modifier = Modifier.size(28.dp) modifier = Modifier.size(32.dp)
) { ) {
Icon( Icon(
Icons.Default.MoreVert, Icons.Default.MoreVert,
contentDescription = "Menu", contentDescription = "Menu",
tint = AppColors.OnSurfaceVariant, tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp) modifier = Modifier.size(20.dp)
) )
} }
@ -157,6 +213,36 @@ fun NoteCard(
onDismissRequest = { showMenu = false }, onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated) 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( DropdownMenuItem(
text = { text = {
Row( Row(
@ -208,23 +294,9 @@ fun NoteCard(
) )
} }
} }
// Pin Button
IconButton(
onClick = onPinClick,
modifier = Modifier.size(28.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)
)
}
}
} }
// Deskripsi Preview // Description Preview
if (note.description.isNotEmpty()) { if (note.description.isNotEmpty()) {
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
@ -238,7 +310,6 @@ fun NoteCard(
fontSize = 14.sp fontSize = 14.sp
) )
} else { } else {
// Tampilkan placeholder jika deskripsi kosong
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text( Text(
@ -272,6 +343,22 @@ fun NoteCard(
color = AppColors.OnSurfaceTertiary, color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp 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 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 import androidx.compose.ui.graphics.Color
object Constants { object AppColors {
// App Info // Primary Colors
const val APP_NAME = "NotesAI" val Primary = Color(0xFF6C63FF)
const val APP_VERSION = "1.1.0" val Secondary = Color(0xFF03DAC6)
val Accent = Color(0xFFFF6B9D)
// DataStore // Background Colors
const val DATASTORE_NAME = "notes_prefs" val Background = Color(0xFF121212)
const val DEBOUNCE_DELAY = 500L val Surface = Color(0xFF1E1E1E)
val SurfaceVariant = Color(0xFF2A2A2A)
// 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 SurfaceElevated = Color(0xFF252525)
val Primary = Color(0xFF3B82F6)
val PrimaryVariant = Color(0xFF60A5FA) // Text Colors
val PrimaryContainer = Color(0xFF1E3A8A) val OnBackground = Color(0xFFE1E1E1)
val Secondary = Color(0xFF8B5CF6) val OnSurface = Color(0xFFCCCCCC)
val SecondaryVariant = Color(0xFFA78BFA) val OnSurfaceVariant = Color(0xFF9E9E9E)
val OnBackground = Color(0xFFFFFFFF) val OnSurfaceTertiary = Color(0xFF757575)
val OnSurface = Color(0xFFE5E5E5)
val OnSurfaceVariant = Color(0xFF9CA3AF) // Utility Colors
val OnSurfaceTertiary = Color(0xFF6B7280) val Error = Color(0xFFCF6679)
val Success = Color(0xFF10B981) val Warning = Color(0xFFFFB74D)
val Error = Color(0xFFEF4444) val Success = Color(0xFF81C784)
val Warning = Color(0xFFFBBF24) val Info = Color(0xFF64B5F6)
val Info = Color(0xFF3B82F6)
val Border = Color(0xFF2A2A2A) // Border & Divider
val Divider = Color(0xFF1F1F1F) val Border = Color(0xFF3A3A3A)
val Overlay = Color(0xFF000000).copy(alpha = 0.5f) val Divider = Color(0xFF2E2E2E)
val Shadow = Color(0xFF000000).copy(alpha = 0.3f)
} }
// LIGHT THEME COLORS object Constants {
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 // Animation Durations
const val ANIMATION_DURATION_SHORT = 150 const val ANIMATION_DURATION_SHORT = 150
const val ANIMATION_DURATION_MEDIUM = 300 const val ANIMATION_DURATION_MEDIUM = 300
const val ANIMATION_DURATION_LONG = 500 const val ANIMATION_DURATION_LONG = 500
const val FADE_IN_DURATION = 200
const val FADE_OUT_DURATION = 200
// Spacing System // Spacing values
object Spacing { object Spacing {
const val ExtraSmall = 4 const val ExtraSmall = 4
const val Small = 8 const val Small = 8
const val Medium = 16 const val Medium = 12
const val Large = 24 const val Large = 16
const val ExtraLarge = 32 const val ExtraLarge = 24
const val XXLarge = 48 const val ExtraExtraLarge = 32
} }
// Corner Radius // Border Radius values
object Radius { object Radius {
const val Small = 8 const val Small = 8
const val Medium = 12 const val Medium = 12
const val Large = 16 const val Large = 16
const val ExtraLarge = 20 const val ExtraLarge = 24
const val Round = 999 const val ExtraExtraLarge = 32
} }
// Elevation // Elevation values
object Elevation { object Elevation {
const val None = 0 const val None = 0
const val Small = 2 const val Small = 2
const val Medium = 4 const val Medium = 4
const val Large = 8 const val Large = 8
const val ExtraLarge = 16 const val ExtraLarge = 12
}
} }
// REACTIVE APP COLORS - Using Compose State // Reference to AppColors for compatibility
object AppColors { val AppColors = com.example.notesai.util.AppColors
// Internal state
private var _isDark by mutableStateOf(true)
// Public setter // Category gradient colors
fun setTheme(isDark: Boolean) { val CategoryColors = listOf(
_isDark = isDark // Purple gradients
} 0xFF6750A4L to 0xFF7E57C2L,
0xFF9C27B0L to 0xFFE91E63L,
// All colors are now reactive via mutableStateOf
val Background: Color // Blue gradients
get() = if (_isDark) Constants.DarkColors.Background else Constants.LightColors.Background 0xFF2196F3L to 0xFF03A9F4L,
0xFF1976D2L to 0xFF4FC3F7L,
val Surface: Color
get() = if (_isDark) Constants.DarkColors.Surface else Constants.LightColors.Surface // Green gradients
0xFF4CAF50L to 0xFF8BC34AL,
val SurfaceVariant: Color 0xFF009688L to 0xFF00BCD4L,
get() = if (_isDark) Constants.DarkColors.SurfaceVariant else Constants.LightColors.SurfaceVariant
// Orange gradients
val SurfaceElevated: Color 0xFFFF9800L to 0xFFFFB74DL,
get() = if (_isDark) Constants.DarkColors.SurfaceElevated else Constants.LightColors.SurfaceElevated 0xFFFF5722L to 0xFFFF7043L,
val Primary: Color // Red gradients
get() = if (_isDark) Constants.DarkColors.Primary else Constants.LightColors.Primary 0xFFF44336L to 0xFFE91E63L,
0xFFD32F2FL to 0xFFFF5252L,
val PrimaryVariant: Color
get() = if (_isDark) Constants.DarkColors.PrimaryVariant else Constants.LightColors.PrimaryVariant // Teal gradients
0xFF009688L to 0xFF26A69AL,
val PrimaryContainer: Color 0xFF00897BL to 0xFF4DB6ACL,
get() = if (_isDark) Constants.DarkColors.PrimaryContainer else Constants.LightColors.PrimaryContainer
// Indigo gradients
val Secondary: Color 0xFF3F51B5L to 0xFF5C6BC0L,
get() = if (_isDark) Constants.DarkColors.Secondary else Constants.LightColors.Secondary 0xFF303F9FL to 0xFF7986CBL,
val SecondaryVariant: Color // Amber gradients
get() = if (_isDark) Constants.DarkColors.SecondaryVariant else Constants.LightColors.SecondaryVariant 0xFFFFC107L to 0xFFFFD54FL,
0xFFFFB300L to 0xFFFFCA28L,
val OnBackground: Color
get() = if (_isDark) Constants.DarkColors.OnBackground else Constants.LightColors.OnBackground // Pink gradients
0xFFE91E63L to 0xFFF06292L,
val OnSurface: Color 0xFFC2185BL to 0xFFEC407AL,
get() = if (_isDark) Constants.DarkColors.OnSurface else Constants.LightColors.OnSurface
// Cyan gradients
val OnSurfaceVariant: Color 0xFF00BCD4L to 0xFF26C6DAL,
get() = if (_isDark) Constants.DarkColors.OnSurfaceVariant else Constants.LightColors.OnSurfaceVariant 0xFF0097A7L to 0xFF00ACC1L,
val OnSurfaceTertiary: Color // Deep Purple gradients
get() = if (_isDark) Constants.DarkColors.OnSurfaceTertiary else Constants.LightColors.OnSurfaceTertiary 0xFF673AB7L to 0xFF9575CDL,
0xFF512DA8L to 0xFF7E57C2L,
val Success: Color
get() = if (_isDark) Constants.DarkColors.Success else Constants.LightColors.Success // Lime gradients
0xFFCDDC39L to 0xFFD4E157L,
val Error: Color 0xFFAFB42BL to 0xFFC0CA33L
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
} }