Menambahkan fitur pin dikategori dan catatan
This commit is contained in:
parent
3692a291c7
commit
85a2c65017
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@ -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>
|
||||||
|
|||||||
@ -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}"
|
||||||
@ -106,4 +59,59 @@ android {
|
|||||||
excludes += "/META-INF/*.kotlin_module"
|
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")
|
||||||
}
|
}
|
||||||
@ -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(
|
||||||
@ -530,4 +529,6 @@ fun NotesApp() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun AppColors.setTheme(darkTheme: Boolean) {}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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.*
|
||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
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.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
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
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.notesai.util.AppColors
|
import com.example.notesai.util.AppColors
|
||||||
import com.example.notesai.util.Constants
|
import com.example.notesai.util.Constants
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryDialog(
|
fun CategoryDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onSave: (String, Long, Long) -> Unit
|
onSave: (String, Long, Long) -> Unit
|
||||||
) {
|
) {
|
||||||
var name by remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
var selectedGradient by remember { mutableStateOf(0) }
|
var selectedGradient by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
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
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
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 },
|
||||||
label = {
|
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)
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
"Pilih Warna:",
|
"Nama Kategori",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
color = AppColors.OnSurfaceVariant
|
||||||
color = AppColors.OnSurface,
|
)
|
||||||
fontWeight = FontWeight.SemiBold,
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
"Contoh: Pekerjaan, Pribadi...",
|
||||||
|
color = AppColors.OnSurfaceTertiary,
|
||||||
fontSize = 14.sp
|
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 ->
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
// Color picker title
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
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 ->
|
// Checkmark with animation
|
||||||
val globalIndex = Constants.CategoryColors.indexOf(gradient)
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
val isSelected = selectedGradient == globalIndex
|
visible = isSelected,
|
||||||
|
enter = scaleIn(
|
||||||
// Scale animation
|
|
||||||
val scale by animateFloatAsState(
|
|
||||||
targetValue = if (isSelected) 1.1f else 1f,
|
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
stiffness = Spring.StiffnessMedium
|
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(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (name.isNotBlank()) {
|
if (name.isNotBlank()) {
|
||||||
@ -164,29 +175,17 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
|
}
|
||||||
@ -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(
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,74 +160,126 @@ fun CategoryCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Menu Button
|
// NEW: Pin Indicator & Menu Button
|
||||||
Box {
|
Row(
|
||||||
IconButton(
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
onClick = { showMenu = true },
|
verticalAlignment = Alignment.CenterVertically
|
||||||
modifier = Modifier.size(32.dp)
|
) {
|
||||||
|
// Pin indicator (only show if pinned)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = category.isPinned,
|
||||||
|
enter = scaleIn() + fadeIn(),
|
||||||
|
exit = scaleOut() + fadeOut()
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.MoreVert,
|
Icons.Default.PushPin,
|
||||||
contentDescription = "Menu",
|
contentDescription = "Pinned",
|
||||||
tint = AppColors.OnSurfaceVariant,
|
tint = AppColors.Warning,
|
||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier.size(20.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
// Menu Button
|
||||||
expanded = showMenu,
|
Box {
|
||||||
onDismissRequest = { showMenu = false },
|
IconButton(
|
||||||
modifier = Modifier.background(AppColors.SurfaceElevated)
|
onClick = { showMenu = true },
|
||||||
) {
|
modifier = Modifier.size(32.dp)
|
||||||
DropdownMenuItem(
|
) {
|
||||||
text = {
|
Icon(
|
||||||
Row(
|
Icons.Default.MoreVert,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
contentDescription = "Menu",
|
||||||
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
|
tint = AppColors.OnSurfaceVariant,
|
||||||
) {
|
modifier = Modifier.size(20.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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenuItem(
|
DropdownMenu(
|
||||||
text = {
|
expanded = showMenu,
|
||||||
Row(
|
onDismissRequest = { showMenu = false },
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.background(AppColors.SurfaceElevated)
|
||||||
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
|
) {
|
||||||
) {
|
// NEW: Pin/Unpin Menu Item
|
||||||
Icon(
|
DropdownMenuItem(
|
||||||
Icons.Default.Delete,
|
text = {
|
||||||
contentDescription = null,
|
Row(
|
||||||
tint = AppColors.Error,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.size(18.dp)
|
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
|
||||||
)
|
) {
|
||||||
Text(
|
Icon(
|
||||||
"Pindah ke Sampah",
|
Icons.Default.PushPin,
|
||||||
color = AppColors.OnSurface,
|
contentDescription = null,
|
||||||
fontSize = 14.sp
|
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
|
HorizontalDivider(color = AppColors.Divider)
|
||||||
showDeleteConfirm = true
|
|
||||||
}
|
// 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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,116 +137,166 @@ 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
|
||||||
Text(
|
Row(
|
||||||
note.title,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.OnBackground,
|
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
maxLines = 2,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
overflow = TextOverflow.Ellipsis,
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
fontSize = 18.sp
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vertical Actions Stack
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.End,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(0.dp)
|
|
||||||
) {
|
) {
|
||||||
// Menu Button
|
Text(
|
||||||
Box {
|
note.title,
|
||||||
IconButton(
|
style = MaterialTheme.typography.titleLarge,
|
||||||
onClick = { showMenu = true },
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.size(28.dp)
|
color = AppColors.OnBackground,
|
||||||
) {
|
maxLines = 2,
|
||||||
Icon(
|
overflow = TextOverflow.Ellipsis,
|
||||||
Icons.Default.MoreVert,
|
fontSize = 18.sp,
|
||||||
contentDescription = "Menu",
|
modifier = Modifier.weight(1f, fill = false)
|
||||||
tint = AppColors.OnSurfaceVariant,
|
)
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
// Pin Badge next to title
|
||||||
expanded = showMenu,
|
AnimatedVisibility(
|
||||||
onDismissRequest = { showMenu = false },
|
visible = note.isPinned,
|
||||||
modifier = Modifier.background(AppColors.SurfaceElevated)
|
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(
|
Row(
|
||||||
text = {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
Row(
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 3.dp)
|
||||||
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
|
) {
|
||||||
) {
|
Icon(
|
||||||
Icon(
|
Icons.Filled.PushPin,
|
||||||
Icons.Default.Edit,
|
contentDescription = "Disematkan",
|
||||||
contentDescription = null,
|
tint = AppColors.Warning,
|
||||||
tint = AppColors.Primary,
|
modifier = Modifier
|
||||||
modifier = Modifier.size(18.dp)
|
.size(12.dp)
|
||||||
)
|
.rotate(45f)
|
||||||
Text(
|
)
|
||||||
"Edit Catatan",
|
Text(
|
||||||
color = AppColors.OnSurface,
|
"Pin",
|
||||||
fontSize = 14.sp
|
fontSize = 10.sp,
|
||||||
)
|
fontWeight = FontWeight.Bold,
|
||||||
}
|
color = AppColors.Warning
|
||||||
},
|
)
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pin Button
|
// Menu Button
|
||||||
|
Box {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onPinClick,
|
onClick = { showMenu = true },
|
||||||
modifier = Modifier.size(28.dp)
|
modifier = Modifier.size(32.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
|
Icons.Default.MoreVert,
|
||||||
contentDescription = "Pin",
|
contentDescription = "Menu",
|
||||||
tint = if (note.isPinned) AppColors.Warning else AppColors.OnSurfaceVariant,
|
tint = AppColors.OnSurfaceVariant,
|
||||||
modifier = Modifier.size(18.dp)
|
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()) {
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
app/src/main/java/com/example/notesai/util/AppColors.kt
Normal file
3
app/src/main/java/com/example/notesai/util/AppColors.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import com.example.notesai.util.AppColors
|
||||||
|
|
||||||
|
annotation class AppColors
|
||||||
@ -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 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 {
|
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
|
// 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
|
|
||||||
object AppColors {
|
|
||||||
// Internal state
|
|
||||||
private var _isDark by mutableStateOf(true)
|
|
||||||
|
|
||||||
// Public setter
|
|
||||||
fun setTheme(isDark: Boolean) {
|
|
||||||
_isDark = isDark
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All colors are now reactive via mutableStateOf
|
// Reference to AppColors for compatibility
|
||||||
val Background: Color
|
val AppColors = com.example.notesai.util.AppColors
|
||||||
get() = if (_isDark) Constants.DarkColors.Background else Constants.LightColors.Background
|
|
||||||
|
|
||||||
val Surface: Color
|
// Category gradient colors
|
||||||
get() = if (_isDark) Constants.DarkColors.Surface else Constants.LightColors.Surface
|
val CategoryColors = listOf(
|
||||||
|
// Purple gradients
|
||||||
|
0xFF6750A4L to 0xFF7E57C2L,
|
||||||
|
0xFF9C27B0L to 0xFFE91E63L,
|
||||||
|
|
||||||
val SurfaceVariant: Color
|
// Blue gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.SurfaceVariant else Constants.LightColors.SurfaceVariant
|
0xFF2196F3L to 0xFF03A9F4L,
|
||||||
|
0xFF1976D2L to 0xFF4FC3F7L,
|
||||||
|
|
||||||
val SurfaceElevated: Color
|
// Green gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.SurfaceElevated else Constants.LightColors.SurfaceElevated
|
0xFF4CAF50L to 0xFF8BC34AL,
|
||||||
|
0xFF009688L to 0xFF00BCD4L,
|
||||||
|
|
||||||
val Primary: Color
|
// Orange gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.Primary else Constants.LightColors.Primary
|
0xFFFF9800L to 0xFFFFB74DL,
|
||||||
|
0xFFFF5722L to 0xFFFF7043L,
|
||||||
|
|
||||||
val PrimaryVariant: Color
|
// Red gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.PrimaryVariant else Constants.LightColors.PrimaryVariant
|
0xFFF44336L to 0xFFE91E63L,
|
||||||
|
0xFFD32F2FL to 0xFFFF5252L,
|
||||||
|
|
||||||
val PrimaryContainer: Color
|
// Teal gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.PrimaryContainer else Constants.LightColors.PrimaryContainer
|
0xFF009688L to 0xFF26A69AL,
|
||||||
|
0xFF00897BL to 0xFF4DB6ACL,
|
||||||
|
|
||||||
val Secondary: Color
|
// Indigo gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.Secondary else Constants.LightColors.Secondary
|
0xFF3F51B5L to 0xFF5C6BC0L,
|
||||||
|
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
|
// Pink gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.OnBackground else Constants.LightColors.OnBackground
|
0xFFE91E63L to 0xFFF06292L,
|
||||||
|
0xFFC2185BL to 0xFFEC407AL,
|
||||||
|
|
||||||
val OnSurface: Color
|
// Cyan gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.OnSurface else Constants.LightColors.OnSurface
|
0xFF00BCD4L to 0xFF26C6DAL,
|
||||||
|
0xFF0097A7L to 0xFF00ACC1L,
|
||||||
|
|
||||||
val OnSurfaceVariant: Color
|
// Deep Purple gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.OnSurfaceVariant else Constants.LightColors.OnSurfaceVariant
|
0xFF673AB7L to 0xFF9575CDL,
|
||||||
|
0xFF512DA8L to 0xFF7E57C2L,
|
||||||
|
|
||||||
val OnSurfaceTertiary: Color
|
// Lime gradients
|
||||||
get() = if (_isDark) Constants.DarkColors.OnSurfaceTertiary else Constants.LightColors.OnSurfaceTertiary
|
0xFFCDDC39L to 0xFFD4E157L,
|
||||||
|
0xFFAFB42BL to 0xFFC0CA33L
|
||||||
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
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user