Menambahkan Dark/Light theme toggle

This commit is contained in:
202310715082 FAZRI ABDURRAHMAN 2025-12-18 11:25:34 +07:00
parent 0876c82abc
commit 1b5e79166c
18 changed files with 522 additions and 300 deletions

View File

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

View File

@ -39,11 +39,13 @@ import com.example.notesai.data.model.Note
import com.example.notesai.data.model.Category
import com.example.notesai.util.updateWhere
import kotlinx.coroutines.delay
import com.example.notesai.util.AppColors
// File: MainActivity.kt (Bagian Theme Setup)
// Ganti MaterialTheme di setContent dengan ini:
import com.example.notesai.util.Constants
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -52,34 +54,34 @@ class MainActivity : ComponentActivity() {
MaterialTheme(
colorScheme = darkColorScheme(
// Primary colors
primary = Constants.AppColors.Primary,
primary = AppColors.Primary,
onPrimary = Color.White,
primaryContainer = Constants.AppColors.PrimaryContainer,
primaryContainer = AppColors.PrimaryContainer,
onPrimaryContainer = Color.White,
// Secondary colors
secondary = Constants.AppColors.Secondary,
secondary = AppColors.Secondary,
onSecondary = Color.White,
secondaryContainer = Constants.AppColors.SecondaryVariant,
secondaryContainer = AppColors.SecondaryVariant,
onSecondaryContainer = Color.White,
// Background colors
background = Constants.AppColors.Background,
onBackground = Constants.AppColors.OnBackground,
background = AppColors.Background,
onBackground = AppColors.OnBackground,
// Surface colors
surface = Constants.AppColors.Surface,
onSurface = Constants.AppColors.OnSurface,
surfaceVariant = Constants.AppColors.SurfaceVariant,
onSurfaceVariant = Constants.AppColors.OnSurfaceVariant,
surface = AppColors.Surface,
onSurface = AppColors.OnSurface,
surfaceVariant = AppColors.SurfaceVariant,
onSurfaceVariant = AppColors.OnSurfaceVariant,
// Error colors
error = Constants.AppColors.Error,
error = AppColors.Error,
onError = Color.White,
// Other
outline = Constants.AppColors.Border,
outlineVariant = Constants.AppColors.Divider
outline = AppColors.Border,
outlineVariant = AppColors.Divider
),
typography = Typography(
// Improve typography for better readability
@ -131,6 +133,17 @@ fun NotesApp() {
var showFullScreenNote by remember { mutableStateOf(false) }
var fullScreenNote by remember { mutableStateOf<Note?>(null) }
// Theme state
var isDarkTheme by remember { mutableStateOf(true) }
// Load theme preference - SET THEME HERE
LaunchedEffect(Unit) {
dataStoreManager.themeFlow.collect { theme ->
isDarkTheme = theme == "dark"
AppColors.setTheme(isDarkTheme) // SET GLOBAL THEME
}
}
// Load data dari DataStore
LaunchedEffect(Unit) {
try {
@ -176,12 +189,10 @@ fun NotesApp() {
}
}
// Provide theme colors
Box(modifier = Modifier.fillMaxSize()) {
Scaffold(
// Di MainActivity.kt, ubah bagian topBar menjadi:
topBar = {
// Hide TopBar untuk AI Helper screen dan FullScreen Note
if (!showFullScreenNote && currentScreen != "ai") {
ModernTopBar(
title = when(currentScreen) {
@ -210,12 +221,7 @@ fun NotesApp() {
floatingActionButton = {
AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
) + fadeIn(),
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
FloatingActionButton(
@ -227,19 +233,10 @@ fun NotesApp() {
showCategoryDialog = true
}
},
containerColor = Constants.AppColors.Primary,
contentColor = Color.White,
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 8.dp,
pressedElevation = 12.dp
),
modifier = Modifier.size(64.dp)
containerColor = AppColors.Primary,
contentColor = Color.White
) {
Icon(
Icons.Default.Add,
contentDescription = if (selectedCategory != null) "Tambah Catatan" else "Tambah Kategori",
modifier = Modifier.size(28.dp)
)
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
},
@ -254,7 +251,8 @@ fun NotesApp() {
onAIClick = { currentScreen = "ai" }
)
}
}
},
containerColor = AppColors.Background // SET BACKGROUND HERE
) { padding ->
Box(modifier = Modifier.fillMaxSize()) {
if (showFullScreenNote && fullScreenNote != null) {
@ -486,19 +484,15 @@ fun NotesApp() {
}
}
// Drawer with Animation - DI LUAR SCAFFOLD agar di atas semua
// Drawer with theme toggle
AnimatedVisibility(
visible = drawerState,
enter = fadeIn() + slideInHorizontally(
initialOffsetX = { -it }
),
exit = fadeOut() + slideOutHorizontally(
targetOffsetX = { -it }
),
modifier = Modifier.zIndex(100f) // Z-index tinggi
enter = fadeIn() + slideInHorizontally { -it },
exit = fadeOut() + slideOutHorizontally { -it }
) {
DrawerMenu(
currentScreen = currentScreen,
isDarkTheme = isDarkTheme,
onDismiss = { drawerState = false },
onItemClick = { screen ->
currentScreen = screen
@ -506,9 +500,15 @@ fun NotesApp() {
drawerState = false
showSearch = false
searchQuery = ""
},
onThemeToggle = {
isDarkTheme = !isDarkTheme
AppColors.setTheme(isDarkTheme) // UPDATE THEME
scope.launch {
dataStoreManager.saveTheme(if (isDarkTheme) "dark" else "light")
}
}
)
}
}
}
}

View File

@ -50,7 +50,8 @@ class DataStoreManager(private val context: Context) {
companion object {
val CATEGORIES_KEY = stringPreferencesKey("categories")
val NOTES_KEY = stringPreferencesKey("notes")
val CHAT_HISTORY_KEY = stringPreferencesKey("chat_history") // NEW
val CHAT_HISTORY_KEY = stringPreferencesKey("chat_history")
val THEME_KEY = stringPreferencesKey("theme") // NEW: "dark" or "light"
}
private val json = Json {
@ -219,4 +220,28 @@ class DataStoreManager(private val context: Context) {
e.printStackTrace()
}
}
// NEW: Theme Preference Flow
val themeFlow: Flow<String> = context.dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
preferences[THEME_KEY] ?: "dark" // Default dark theme
}
// NEW: Save Theme Preference
suspend fun saveTheme(theme: String) {
try {
context.dataStore.edit { preferences ->
preferences[THEME_KEY] = theme
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@ -1,8 +1,11 @@
package com.example.notesai.data.model
import android.annotation.SuppressLint
//noinspection UnsafeOptInUsageError
import kotlinx.serialization.Serializable
import java.util.UUID
@SuppressLint("UnsafeOptInUsageError")
@Serializable
data class Note(
val id: String = UUID.randomUUID().toString(),

View File

@ -26,6 +26,7 @@ import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.ChatHistory
import com.example.notesai.data.model.Category
import com.example.notesai.data.model.Note
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
import java.text.SimpleDateFormat
import java.util.*
@ -56,7 +57,7 @@ fun ChatHistoryDrawer(
Box(
modifier = Modifier
.fillMaxSize()
.background(Constants.AppColors.Overlay)
.background(AppColors.Overlay)
.clickable(
onClick = onDismiss,
indication = null,
@ -74,7 +75,7 @@ fun ChatHistoryDrawer(
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
color = Constants.AppColors.Surface,
color = AppColors.Surface,
shadowElevation = Constants.Elevation.ExtraLarge.dp
) {
Column(
@ -87,7 +88,7 @@ fun ChatHistoryDrawer(
.background(
brush = Brush.verticalGradient(
colors = listOf(
Constants.AppColors.Primary.copy(alpha = 0.15f),
AppColors.Primary.copy(alpha = 0.15f),
Color.Transparent
)
)
@ -103,7 +104,7 @@ fun ChatHistoryDrawer(
modifier = Modifier
.size(48.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.2f),
color = AppColors.Primary.copy(alpha = 0.2f),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
),
contentAlignment = Alignment.Center
@ -111,7 +112,7 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.History,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(24.dp)
)
}
@ -119,7 +120,7 @@ fun ChatHistoryDrawer(
Text(
"Riwayat Chat",
style = MaterialTheme.typography.headlineSmall,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
}
@ -129,7 +130,7 @@ fun ChatHistoryDrawer(
Text(
"${chatHistories.size} percakapan tersimpan",
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
}
}
@ -145,7 +146,7 @@ fun ChatHistoryDrawer(
Text(
"Filter Kategori",
style = MaterialTheme.typography.labelMedium,
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
@ -156,7 +157,7 @@ fun ChatHistoryDrawer(
onClick = { showCategoryDropdown = !showCategoryDropdown },
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant
containerColor = AppColors.SurfaceVariant
),
shape = RoundedCornerShape(12.dp)
) {
@ -174,12 +175,12 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
selectedCategory?.name ?: "Semua Kategori",
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
fontSize = 14.sp
@ -188,7 +189,7 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
tint = Constants.AppColors.OnSurfaceVariant
tint = AppColors.OnSurfaceVariant
)
}
}
@ -198,7 +199,7 @@ fun ChatHistoryDrawer(
onDismissRequest = { showCategoryDropdown = false },
modifier = Modifier
.width(280.dp)
.background(Constants.AppColors.SurfaceElevated)
.background(AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = {
@ -209,12 +210,12 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Constants.AppColors.OnSurfaceVariant,
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp)
)
Text(
"Semua Kategori",
color = Constants.AppColors.OnSurface
color = AppColors.OnSurface
)
}
},
@ -228,7 +229,7 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.Check,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(20.dp)
)
}
@ -236,7 +237,7 @@ fun ChatHistoryDrawer(
)
if (categoriesWithNotes.isNotEmpty()) {
HorizontalDivider(color = Constants.AppColors.Divider)
HorizontalDivider(color = AppColors.Divider)
}
categoriesWithNotes.forEach { category ->
@ -263,12 +264,12 @@ fun ChatHistoryDrawer(
)
Text(
category.name,
color = Constants.AppColors.OnSurface
color = AppColors.OnSurface
)
}
Surface(
color = Constants.AppColors.Primary.copy(alpha = 0.15f),
color = AppColors.Primary.copy(alpha = 0.15f),
shape = RoundedCornerShape(Constants.Radius.Small.dp)
) {
Text(
@ -277,7 +278,7 @@ fun ChatHistoryDrawer(
horizontal = 8.dp,
vertical = 2.dp
),
color = Constants.AppColors.Primary,
color = AppColors.Primary,
fontSize = 11.sp,
fontWeight = FontWeight.Bold
)
@ -294,7 +295,7 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.Check,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(20.dp)
)
}
@ -308,7 +309,7 @@ fun ChatHistoryDrawer(
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
HorizontalDivider(
color = Constants.AppColors.Divider,
color = AppColors.Divider,
modifier = Modifier.padding(horizontal = Constants.Spacing.Large.dp)
)
@ -335,18 +336,18 @@ fun ChatHistoryDrawer(
Icons.Default.ChatBubbleOutline,
contentDescription = null,
modifier = Modifier.size(48.dp),
tint = Constants.AppColors.OnSurfaceVariant
tint = AppColors.OnSurfaceVariant
)
Text(
"Belum ada riwayat chat",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
fontWeight = FontWeight.Medium
)
Text(
"Mulai chat dengan AI",
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary
color = AppColors.OnSurfaceTertiary
)
}
}
@ -368,19 +369,19 @@ fun ChatHistoryDrawer(
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(16.dp)
)
Text(
categoryInfo.second,
style = MaterialTheme.typography.labelLarge,
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp
)
HorizontalDivider(
modifier = Modifier.weight(1f),
color = Constants.AppColors.Divider
color = AppColors.Divider
)
}
}
@ -413,7 +414,7 @@ private fun ChatHistoryItem(
onClick = onClick,
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant
containerColor = AppColors.SurfaceVariant
),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
) {
@ -429,7 +430,7 @@ private fun ChatHistoryItem(
) {
// Message Count
Surface(
color = Constants.AppColors.Primary.copy(alpha = 0.15f),
color = AppColors.Primary.copy(alpha = 0.15f),
shape = RoundedCornerShape(Constants.Radius.Small.dp)
) {
Row(
@ -443,12 +444,12 @@ private fun ChatHistoryItem(
Icon(
Icons.Default.Chat,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(12.dp)
)
Text(
"${history.messages.size}",
color = Constants.AppColors.Primary,
color = AppColors.Primary,
fontSize = 11.sp,
fontWeight = FontWeight.Bold
)
@ -463,7 +464,7 @@ private fun ChatHistoryItem(
Icon(
Icons.Default.Delete,
contentDescription = "Delete",
tint = Constants.AppColors.Error,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
}
@ -475,7 +476,7 @@ private fun ChatHistoryItem(
Text(
history.lastMessagePreview,
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
fontSize = 14.sp
@ -487,7 +488,7 @@ private fun ChatHistoryItem(
Text(
dateFormat.format(Date(history.timestamp)),
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
}
@ -501,7 +502,7 @@ private fun ChatHistoryItem(
Icon(
Icons.Default.DeleteForever,
contentDescription = null,
tint = Constants.AppColors.Error
tint = AppColors.Error
)
},
title = {
@ -520,7 +521,7 @@ private fun ChatHistoryItem(
showDeleteConfirm = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Error
containerColor = AppColors.Error
)
) {
Text("Hapus")
@ -531,7 +532,7 @@ private fun ChatHistoryItem(
Text("Batal")
}
},
containerColor = Constants.AppColors.SurfaceElevated
containerColor = AppColors.SurfaceElevated
)
}
}

View File

@ -24,18 +24,22 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.Constants
import com.example.notesai.util.AppColors
@Composable
fun DrawerMenu(
currentScreen: String,
isDarkTheme: Boolean,
onDismiss: () -> Unit,
onItemClick: (String) -> Unit
onItemClick: (String) -> Unit,
onThemeToggle: () -> Unit
) {
// Backdrop with blur effect
Box(
modifier = Modifier
.fillMaxSize()
.background(Constants.AppColors.Overlay)
.background(AppColors.Overlay)
.clickable(
onClick = onDismiss,
indication = null,
@ -53,7 +57,7 @@ fun DrawerMenu(
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
color = Constants.AppColors.Surface,
color = AppColors.Surface,
shadowElevation = Constants.Elevation.ExtraLarge.dp
) {
Column(
@ -66,7 +70,7 @@ fun DrawerMenu(
.background(
brush = Brush.verticalGradient(
colors = listOf(
Constants.AppColors.Primary.copy(alpha = 0.15f),
AppColors.Primary.copy(alpha = 0.15f),
Color.Transparent
)
)
@ -79,7 +83,7 @@ fun DrawerMenu(
modifier = Modifier
.size(56.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.2f),
color = AppColors.Primary.copy(alpha = 0.2f),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
),
contentAlignment = Alignment.Center
@ -87,7 +91,7 @@ fun DrawerMenu(
Icon(
Icons.Default.Create,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(32.dp)
)
}
@ -97,7 +101,7 @@ fun DrawerMenu(
Text(
Constants.APP_NAME,
style = MaterialTheme.typography.headlineMedium,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 24.sp
)
@ -107,7 +111,7 @@ fun DrawerMenu(
Text(
"Smart Note Taking",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
fontSize = 14.sp
)
}
@ -144,11 +148,26 @@ fun DrawerMenu(
onClick = { onItemClick("trash") }
)
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
HorizontalDivider(
color = AppColors.Divider,
modifier = Modifier.padding(horizontal = Constants.Spacing.Medium.dp)
)
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
// Theme Toggle
ThemeToggleItem(
isDarkTheme = isDarkTheme,
onToggle = onThemeToggle
)
Spacer(modifier = Modifier.weight(1f))
// Footer - Version info
HorizontalDivider(
color = Constants.AppColors.Divider,
color = AppColors.Divider,
modifier = Modifier.padding(horizontal = Constants.Spacing.Medium.dp)
)
@ -162,13 +181,13 @@ fun DrawerMenu(
Text(
"Version ${Constants.APP_VERSION}",
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
// Powered by badge
Surface(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
color = AppColors.Primary.copy(alpha = 0.1f),
shape = RoundedCornerShape(Constants.Radius.Small.dp)
) {
Row(
@ -179,12 +198,12 @@ fun DrawerMenu(
Icon(
Icons.Default.AutoAwesome,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(12.dp)
)
Text(
"AI",
color = Constants.AppColors.Primary,
color = AppColors.Primary,
fontSize = 11.sp,
fontWeight = FontWeight.Bold
)
@ -196,6 +215,76 @@ fun DrawerMenu(
}
}
@Composable
private fun ThemeToggleItem(
isDarkTheme: Boolean,
onToggle: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Medium.dp)
.clip(RoundedCornerShape(Constants.Radius.Medium.dp))
.clickable(onClick = onToggle)
.background(AppColors.SurfaceVariant)
.padding(horizontal = Constants.Spacing.Medium.dp, vertical = Constants.Spacing.Medium.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Medium.dp)
) {
// Icon with background
Box(
modifier = Modifier
.size(40.dp)
.background(
color = AppColors.Primary.copy(alpha = 0.2f),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
if (isDarkTheme) Icons.Default.DarkMode else Icons.Default.LightMode,
contentDescription = null,
tint = AppColors.Primary,
modifier = Modifier.size(20.dp)
)
}
// Text
Column {
Text(
"Tema Aplikasi",
style = MaterialTheme.typography.bodyLarge,
color = AppColors.OnSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 15.sp
)
Text(
if (isDarkTheme) "Mode Gelap" else "Mode Terang",
style = MaterialTheme.typography.bodySmall,
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
}
}
// Toggle Switch
Switch(
checked = isDarkTheme,
onCheckedChange = { onToggle() },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.White,
checkedTrackColor = AppColors.Primary,
uncheckedThumbColor = Color.White,
uncheckedTrackColor = AppColors.Border
)
)
}
}
@Composable
private fun DrawerMenuItem(
icon: ImageVector,
@ -221,7 +310,7 @@ private fun DrawerMenuItem(
.clickable(onClick = onClick)
.background(
color = if (isSelected)
Constants.AppColors.Primary.copy(alpha = 0.1f)
AppColors.Primary.copy(alpha = 0.1f)
else
Color.Transparent
)
@ -234,9 +323,9 @@ private fun DrawerMenuItem(
.size(40.dp)
.background(
color = if (isSelected)
Constants.AppColors.Primary.copy(alpha = 0.2f)
AppColors.Primary.copy(alpha = 0.2f)
else
Constants.AppColors.SurfaceVariant,
AppColors.SurfaceVariant,
shape = CircleShape
),
contentAlignment = Alignment.Center
@ -244,7 +333,7 @@ private fun DrawerMenuItem(
Icon(
icon,
contentDescription = null,
tint = if (isSelected) Constants.AppColors.Primary else Constants.AppColors.OnSurfaceVariant,
tint = if (isSelected) AppColors.Primary else AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
@ -255,7 +344,7 @@ private fun DrawerMenuItem(
Text(
text,
style = MaterialTheme.typography.bodyLarge,
color = if (isSelected) Constants.AppColors.Primary else Constants.AppColors.OnSurface,
color = if (isSelected) AppColors.Primary else AppColors.OnSurface,
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
fontSize = 15.sp
)

View File

@ -26,6 +26,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@Composable
@ -47,7 +48,7 @@ fun ModernBottomBar(
elevation = Constants.Elevation.Large.dp,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
),
color = Constants.AppColors.SurfaceElevated,
color = AppColors.SurfaceElevated,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
) {
Row(
@ -96,7 +97,7 @@ private fun BottomBarItem(
// Color animation
val iconColor by animateColorAsState(
targetValue = if (selected) Constants.AppColors.Primary else Constants.AppColors.OnSurfaceVariant,
targetValue = if (selected) AppColors.Primary else AppColors.OnSurfaceVariant,
animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM),
label = "color"
)
@ -125,7 +126,7 @@ private fun BottomBarItem(
modifier = Modifier
.size(40.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.15f),
color = AppColors.Primary.copy(alpha = 0.15f),
shape = CircleShape
)
)
@ -148,7 +149,7 @@ private fun BottomBarItem(
Spacer(modifier = Modifier.height(4.dp))
Text(
label,
color = Constants.AppColors.Primary,
color = AppColors.Primary,
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.Bold,
fontSize = 12.sp

View File

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@OptIn(ExperimentalMaterial3Api::class)
@ -48,7 +49,7 @@ fun ModernTopBar(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = Constants.AppColors.Surface
color = AppColors.Surface
) {
Row(
modifier = Modifier
@ -63,16 +64,16 @@ fun ModernTopBar(
placeholder = {
Text(
"Cari catatan atau kategori...",
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
},
colors = TextFieldDefaults.colors(
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
cursorColor = Constants.AppColors.Primary,
focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant,
focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface,
cursorColor = AppColors.Primary,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
@ -96,7 +97,7 @@ fun ModernTopBar(
Icon(
Icons.Default.Close,
contentDescription = "Close Search",
tint = Constants.AppColors.OnSurfaceVariant
tint = AppColors.OnSurfaceVariant
)
}
}
@ -107,7 +108,7 @@ fun ModernTopBar(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = Constants.AppColors.Surface
color = AppColors.Surface
) {
Row(
modifier = Modifier
@ -120,7 +121,7 @@ fun ModernTopBar(
Icon(
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
contentDescription = if (showBackButton) "Back" else "Menu",
tint = Constants.AppColors.OnSurface
tint = AppColors.OnSurface
)
}
@ -129,7 +130,7 @@ fun ModernTopBar(
title,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
modifier = Modifier.weight(1f)
)
@ -138,7 +139,7 @@ fun ModernTopBar(
Icon(
Icons.Default.Search,
contentDescription = "Search",
tint = Constants.AppColors.OnSurfaceVariant
tint = AppColors.OnSurfaceVariant
)
}
}

View File

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@Composable
@ -31,12 +32,12 @@ fun CategoryDialog(
AlertDialog(
onDismissRequest = onDismiss,
containerColor = Constants.AppColors.Surface,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp),
title = {
Text(
"Buat Kategori Baru",
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
@ -52,24 +53,24 @@ fun CategoryDialog(
label = {
Text(
"Nama Kategori",
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
},
placeholder = {
Text(
"Contoh: Pekerjaan, Personal",
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 14.sp
)
},
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
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),
@ -83,18 +84,18 @@ fun CategoryDialog(
Text(
"Pilih Warna:",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp
)
Constants.AppColors.CategoryColors.chunked(4).forEach { row ->
Constants.CategoryColors.chunked(4).forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
row.forEach { gradient ->
val globalIndex = Constants.AppColors.CategoryColors.indexOf(gradient)
val globalIndex = Constants.CategoryColors.indexOf(gradient)
val isSelected = selectedGradient == globalIndex
// Scale animation
@ -120,11 +121,7 @@ fun CategoryDialog(
)
)
)
.clickable { selectedGradient = globalIndex }
.then(
if (isSelected) Modifier
else Modifier
),
.clickable { selectedGradient = globalIndex },
contentAlignment = Alignment.Center
) {
// Check icon dengan animation
@ -158,14 +155,14 @@ fun CategoryDialog(
Button(
onClick = {
if (name.isNotBlank()) {
val gradient = Constants.AppColors.CategoryColors[selectedGradient]
val gradient = Constants.CategoryColors[selectedGradient]
onSave(name, gradient.first, gradient.second)
}
},
enabled = name.isNotBlank(),
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Primary,
disabledContainerColor = Constants.AppColors.Primary.copy(alpha = 0.5f)
containerColor = AppColors.Primary,
disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f)
),
shape = RoundedCornerShape(12.dp),
modifier = Modifier.height(48.dp)
@ -186,7 +183,7 @@ fun CategoryDialog(
) {
Text(
"Batal",
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
}

View File

@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.Note
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@Composable
@ -29,19 +30,19 @@ fun NoteDialog(
if (showDeleteConfirm) {
AlertDialog(
onDismissRequest = { showDeleteConfirm = false },
containerColor = Constants.AppColors.Surface,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp),
title = {
Text(
"Hapus Catatan?",
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
},
text = {
Text(
"Catatan ini akan dipindahkan ke sampah.",
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
},
confirmButton = {
@ -51,7 +52,7 @@ fun NoteDialog(
showDeleteConfirm = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Error
containerColor = AppColors.Error
),
shape = RoundedCornerShape(12.dp)
) {
@ -63,7 +64,7 @@ fun NoteDialog(
onClick = { showDeleteConfirm = false },
shape = RoundedCornerShape(12.dp)
) {
Text("Batal", color = Constants.AppColors.OnSurfaceVariant)
Text("Batal", color = AppColors.OnSurfaceVariant)
}
}
)
@ -71,12 +72,12 @@ fun NoteDialog(
AlertDialog(
onDismissRequest = onDismiss,
containerColor = Constants.AppColors.Surface,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(20.dp),
title = {
Text(
if (note == null) "Catatan Baru" else "Edit Catatan",
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
@ -92,24 +93,24 @@ fun NoteDialog(
label = {
Text(
"Judul",
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
},
placeholder = {
Text(
"Masukkan judul catatan",
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 14.sp
)
},
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
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),
@ -123,13 +124,13 @@ fun NoteDialog(
label = {
Text(
"Deskripsi",
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
},
placeholder = {
Text(
"Tambahkan deskripsi singkat...",
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 14.sp
)
},
@ -137,12 +138,12 @@ fun NoteDialog(
.fillMaxWidth()
.heightIn(min = 120.dp, max = 200.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
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),
@ -163,7 +164,7 @@ fun NoteDialog(
Icon(
Icons.Default.Delete,
contentDescription = "Hapus",
tint = Constants.AppColors.Error
tint = AppColors.Error
)
}
}
@ -178,7 +179,7 @@ fun NoteDialog(
) {
Text(
"Batal",
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
}
@ -192,8 +193,8 @@ fun NoteDialog(
},
enabled = title.isNotBlank(),
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Primary,
disabledContainerColor = Constants.AppColors.Primary.copy(alpha = 0.5f)
containerColor = AppColors.Primary,
disabledContainerColor = AppColors.Primary.copy(alpha = 0.5f)
),
shape = RoundedCornerShape(12.dp),
modifier = Modifier.height(48.dp)
@ -208,4 +209,4 @@ fun NoteDialog(
}
}
)
}
}

View File

@ -34,6 +34,7 @@ import com.example.notesai.presentation.screens.ai.components.ChatBubble
import com.example.notesai.presentation.screens.ai.components.ChatHistoryDrawer
import com.example.notesai.presentation.screens.ai.components.CompactStatItem
import com.example.notesai.presentation.screens.ai.components.SuggestionChip
import com.example.notesai.util.AppColors
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -128,11 +129,11 @@ fun AIHelperScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Constants.AppColors.Background)
.background(AppColors.Background)
) {
// Top Bar with History Button & Stats
Surface(
color = Constants.AppColors.Surface,
color = AppColors.Surface,
shadowElevation = 2.dp
) {
Column(
@ -152,21 +153,21 @@ fun AIHelperScreen(
modifier = Modifier
.size(40.dp)
.background(
Constants.AppColors.Primary.copy(alpha = 0.1f),
AppColors.Primary.copy(alpha = 0.1f),
CircleShape
)
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu",
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(20.dp)
)
}
// Category Badge
Surface(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
color = AppColors.Primary.copy(alpha = 0.1f),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
) {
Row(
@ -180,12 +181,12 @@ fun AIHelperScreen(
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(16.dp)
)
Text(
selectedCategory?.name ?: "Semua Kategori",
color = Constants.AppColors.Primary,
color = AppColors.Primary,
fontSize = 13.sp,
fontWeight = FontWeight.SemiBold
)
@ -197,7 +198,7 @@ fun AIHelperScreen(
Button(
onClick = { startNewChat() },
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Primary
containerColor = AppColors.Primary
),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
contentPadding = PaddingValues(
@ -252,7 +253,7 @@ fun AIHelperScreen(
}
}
HorizontalDivider(color = Constants.AppColors.Divider)
HorizontalDivider(color = AppColors.Divider)
// Chat Area
Box(
@ -273,7 +274,7 @@ fun AIHelperScreen(
modifier = Modifier
.size(80.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
color = AppColors.Primary.copy(alpha = 0.1f),
shape = CircleShape
),
contentAlignment = Alignment.Center
@ -282,7 +283,7 @@ fun AIHelperScreen(
Icons.Default.AutoAwesome,
contentDescription = null,
modifier = Modifier.size(40.dp),
tint = Constants.AppColors.Primary
tint = AppColors.Primary
)
}
@ -291,7 +292,7 @@ fun AIHelperScreen(
Text(
"AI Assistant",
style = MaterialTheme.typography.headlineMedium,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
@ -300,7 +301,7 @@ fun AIHelperScreen(
Text(
"Tanyakan apa saja tentang catatan Anda",
style = MaterialTheme.typography.bodyLarge,
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
textAlign = TextAlign.Center
)
@ -315,7 +316,7 @@ fun AIHelperScreen(
Text(
"Contoh pertanyaan:",
style = MaterialTheme.typography.labelMedium,
color = Constants.AppColors.OnSurfaceTertiary
color = AppColors.OnSurfaceTertiary
)
SuggestionChip("Analisis catatan saya") { prompt = it }
SuggestionChip("Buat ringkasan") { prompt = it }
@ -356,7 +357,7 @@ fun AIHelperScreen(
horizontalArrangement = Arrangement.Start
) {
Surface(
color = Constants.AppColors.SurfaceVariant,
color = AppColors.SurfaceVariant,
shape = RoundedCornerShape(16.dp)
) {
Row(
@ -366,12 +367,12 @@ fun AIHelperScreen(
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = Constants.AppColors.Primary,
color = AppColors.Primary,
strokeWidth = 2.dp
)
Text(
"AI sedang berpikir...",
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
style = MaterialTheme.typography.bodyMedium
)
}
@ -383,7 +384,7 @@ fun AIHelperScreen(
if (errorMessage.isNotEmpty()) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Constants.AppColors.Error.copy(alpha = 0.1f),
color = AppColors.Error.copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
) {
Row(
@ -394,12 +395,12 @@ fun AIHelperScreen(
Icon(
Icons.Default.Warning,
contentDescription = null,
tint = Constants.AppColors.Error,
tint = AppColors.Error,
modifier = Modifier.size(20.dp)
)
Text(
errorMessage,
color = Constants.AppColors.Error,
color = AppColors.Error,
style = MaterialTheme.typography.bodySmall
)
}
@ -411,7 +412,7 @@ fun AIHelperScreen(
// Input Area - Minimalist
Surface(
color = Constants.AppColors.Surface,
color = AppColors.Surface,
shadowElevation = 8.dp,
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
) {
@ -428,19 +429,19 @@ fun AIHelperScreen(
placeholder = {
Text(
"Ketik pesan...",
color = Constants.AppColors.OnSurfaceTertiary
color = AppColors.OnSurfaceTertiary
)
},
modifier = Modifier
.weight(1f)
.heightIn(min = 48.dp, max = 120.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface,
focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant,
cursorColor = AppColors.Primary,
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent
),
shape = RoundedCornerShape(24.dp),
@ -501,7 +502,7 @@ fun AIHelperScreen(
}
}
},
containerColor = Constants.AppColors.Primary,
containerColor = AppColors.Primary,
modifier = Modifier.size(48.dp)
) {
Icon(

View File

@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.ChatMessage
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
import java.text.SimpleDateFormat
import java.util.Date
@ -52,7 +53,7 @@ fun ChatBubble(
modifier = Modifier
.size(32.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
color = AppColors.Primary.copy(alpha = 0.1f),
shape = CircleShape
),
contentAlignment = Alignment.Center
@ -60,7 +61,7 @@ fun ChatBubble(
Icon(
Icons.Default.AutoAwesome,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(16.dp)
)
}
@ -73,9 +74,9 @@ fun ChatBubble(
) {
Surface(
color = if (message.isUser)
Constants.AppColors.Primary
AppColors.Primary
else
Constants.AppColors.SurfaceVariant,
AppColors.SurfaceVariant,
shape = RoundedCornerShape(
topStart = 16.dp,
topEnd = 16.dp,
@ -86,7 +87,7 @@ fun ChatBubble(
Column(modifier = Modifier.padding(12.dp)) {
Text(
message.message,
color = if (message.isUser) Color.White else Constants.AppColors.OnSurface,
color = if (message.isUser) Color.White else AppColors.OnSurface,
style = MaterialTheme.typography.bodyMedium,
lineHeight = 20.sp
)
@ -101,7 +102,7 @@ fun ChatBubble(
color = if (message.isUser)
Color.White.copy(0.7f)
else
Constants.AppColors.OnSurfaceTertiary,
AppColors.OnSurfaceTertiary,
style = MaterialTheme.typography.bodySmall,
fontSize = 11.sp,
modifier = Modifier.padding(top = 4.dp)
@ -115,7 +116,7 @@ fun ChatBubble(
Icon(
Icons.Default.ContentCopy,
contentDescription = "Copy",
tint = Constants.AppColors.OnSurfaceVariant,
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(14.dp)
)
}
@ -127,7 +128,7 @@ fun ChatBubble(
if (showCopied && !message.isUser) {
Text(
"✓ Disalin",
color = Constants.AppColors.Success,
color = AppColors.Success,
style = MaterialTheme.typography.bodySmall,
fontSize = 11.sp,
modifier = Modifier.padding(top = 4.dp, start = 8.dp)

View File

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@Composable
@ -32,7 +33,7 @@ fun CompactStatItem(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier
.background(
color = Constants.AppColors.SurfaceVariant,
color = AppColors.SurfaceVariant,
shape = RoundedCornerShape(8.dp)
)
.padding(horizontal = 12.dp, vertical = 8.dp)
@ -40,21 +41,21 @@ fun CompactStatItem(
Icon(
icon,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(16.dp)
)
Column {
Text(
value,
style = MaterialTheme.typography.titleMedium,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
Text(
label,
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 11.sp
)
}

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@Composable
@ -32,7 +33,7 @@ fun SuggestionChip(
) {
Surface(
onClick = { onSelect(text) },
color = Constants.AppColors.SurfaceVariant,
color = AppColors.SurfaceVariant,
shape = RoundedCornerShape(12.dp)
) {
Row(
@ -43,12 +44,12 @@ fun SuggestionChip(
Icon(
Icons.Default.Lightbulb,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(16.dp)
)
Text(
text,
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
style = MaterialTheme.typography.bodyMedium,
fontSize = 14.sp
)

View File

@ -22,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.Category
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
@Composable
@ -54,14 +55,14 @@ fun CategoryCard(
title = {
Text(
"Pindahkan ke Sampah?",
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
},
text = {
Text(
"Kategori '${category.name}' dan $noteCount catatan di dalamnya akan dipindahkan ke sampah.",
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
},
confirmButton = {
@ -71,7 +72,7 @@ fun CategoryCard(
showDeleteConfirm = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Error
containerColor = AppColors.Error
)
) {
Text("Hapus", color = Color.White)
@ -79,10 +80,10 @@ fun CategoryCard(
},
dismissButton = {
TextButton(onClick = { showDeleteConfirm = false }) {
Text("Batal", color = Constants.AppColors.OnSurfaceVariant)
Text("Batal", color = AppColors.OnSurfaceVariant)
}
},
containerColor = Constants.AppColors.Surface,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(Constants.Radius.Large.dp)
)
}
@ -107,7 +108,7 @@ fun CategoryCard(
.clickable(onClick = onClick),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.Surface
containerColor = AppColors.Surface
),
elevation = CardDefaults.cardElevation(
defaultElevation = Constants.Elevation.Small.dp
@ -167,7 +168,7 @@ fun CategoryCard(
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Constants.AppColors.OnSurfaceVariant,
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
@ -175,7 +176,7 @@ fun CategoryCard(
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(Constants.AppColors.SurfaceElevated)
modifier = Modifier.background(AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = {
@ -186,12 +187,12 @@ fun CategoryCard(
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = Constants.AppColors.Primary,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Kategori",
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
@ -211,12 +212,12 @@ fun CategoryCard(
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = Constants.AppColors.Error,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
@ -237,7 +238,7 @@ fun CategoryCard(
category.name,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontSize = 18.sp
)
@ -251,13 +252,13 @@ fun CategoryCard(
Icon(
Icons.Default.Description,
contentDescription = null,
tint = Constants.AppColors.OnSurfaceTertiary,
tint = AppColors.OnSurfaceTertiary,
modifier = Modifier.size(14.dp)
)
Text(
"$noteCount catatan",
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 13.sp
)
}
@ -275,7 +276,7 @@ fun EditCategoryDialog(
var name by remember { mutableStateOf(category.name) }
var selectedGradient by remember {
mutableStateOf(
Constants.AppColors.CategoryColors.indexOfFirst {
Constants.CategoryColors.indexOfFirst {
it.first == category.gradientStart && it.second == category.gradientEnd
}.takeIf { it >= 0 } ?: 0
)
@ -283,12 +284,12 @@ fun EditCategoryDialog(
AlertDialog(
onDismissRequest = onDismiss,
containerColor = Constants.AppColors.Surface,
containerColor = AppColors.Surface,
shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = {
Text(
"Edit Kategori",
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
},
@ -300,18 +301,18 @@ fun EditCategoryDialog(
label = {
Text(
"Nama Kategori",
color = Constants.AppColors.OnSurfaceVariant
color = AppColors.OnSurfaceVariant
)
},
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
unfocusedBorderColor = Constants.AppColors.Border
focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface,
focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant,
cursorColor = AppColors.Primary,
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = AppColors.Border
),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
)
@ -321,20 +322,20 @@ fun EditCategoryDialog(
Text(
"Pilih Warna:",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurface,
color = AppColors.OnSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(12.dp))
Constants.AppColors.CategoryColors.chunked(4).forEach { row ->
Constants.CategoryColors.chunked(4).forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
row.forEachIndexed { _, gradient ->
val globalIndex = Constants.AppColors.CategoryColors.indexOf(gradient)
val globalIndex = Constants.CategoryColors.indexOf(gradient)
val isSelected = selectedGradient == globalIndex
Box(
@ -376,13 +377,13 @@ fun EditCategoryDialog(
Button(
onClick = {
if (name.isNotBlank()) {
val gradient = Constants.AppColors.CategoryColors[selectedGradient]
val gradient = Constants.CategoryColors[selectedGradient]
onSave(name, gradient.first, gradient.second)
}
},
enabled = name.isNotBlank(),
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Primary
containerColor = AppColors.Primary
)
) {
Text("Simpan", color = Color.White, fontWeight = FontWeight.Bold)
@ -390,7 +391,7 @@ fun EditCategoryDialog(
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Batal", color = Constants.AppColors.OnSurfaceVariant)
Text("Batal", color = AppColors.OnSurfaceVariant)
}
}
)

View File

@ -20,6 +20,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.Note
import com.example.notesai.util.AppColors
import com.example.notesai.util.Constants
import java.text.SimpleDateFormat
import java.util.*
@ -51,7 +52,7 @@ fun NoteCard(
.combinedClickable(onClick = onClick),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant
containerColor = AppColors.SurfaceVariant
),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
@ -73,7 +74,7 @@ fun NoteCard(
note.title,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Constants.AppColors.OnBackground,
color = AppColors.OnBackground,
modifier = Modifier.weight(1f),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
@ -88,7 +89,7 @@ fun NoteCard(
Icon(
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
contentDescription = "Pin",
tint = if (note.isPinned) Constants.AppColors.Warning else Constants.AppColors.OnSurfaceVariant,
tint = if (note.isPinned) AppColors.Warning else AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp)
)
}
@ -103,7 +104,7 @@ fun NoteCard(
style = MaterialTheme.typography.bodyMedium,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
color = Constants.AppColors.OnSurfaceVariant,
color = AppColors.OnSurfaceVariant,
lineHeight = 20.sp,
fontSize = 14.sp
)
@ -114,7 +115,7 @@ fun NoteCard(
Text(
"Tidak ada deskripsi",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurfaceVariant.copy(alpha = 0.5f),
color = AppColors.OnSurfaceVariant.copy(alpha = 0.5f),
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic,
fontSize = 14.sp
)
@ -124,7 +125,7 @@ fun NoteCard(
// Divider
HorizontalDivider(
color = Constants.AppColors.Divider,
color = AppColors.Divider,
thickness = 1.dp
)
@ -139,7 +140,7 @@ fun NoteCard(
Text(
dateFormat.format(Date(note.timestamp)),
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
color = AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
}

View File

@ -1,5 +1,8 @@
package com.example.notesai.util
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
object Constants {
@ -16,56 +19,68 @@ object Constants {
const val MAX_CHAT_PREVIEW_LINES = 2
const val GRID_COLUMNS = 2
// NEW MINIMALIST COLOR PALETTE
object AppColors {
// Backgrounds - Neutral Dark
val Background = Color(0xFF0A0A0A) // Almost black
val Surface = Color(0xFF141414) // Dark gray
val SurfaceVariant = Color(0xFF1E1E1E) // Lighter dark gray
val SurfaceElevated = Color(0xFF252525) // Elevated surface
// Primary Accent - Subtle Blue (minimalist)
val Primary = Color(0xFF3B82F6) // Modern blue
val PrimaryVariant = Color(0xFF60A5FA) // Light blue
val PrimaryContainer = Color(0xFF1E3A8A) // Dark blue container
// Secondary Accent - Minimal use
val Secondary = Color(0xFF8B5CF6) // Subtle purple
val SecondaryVariant = Color(0xFFA78BFA) // Light purple
// Text Colors - High contrast for readability
val OnBackground = Color(0xFFFFFFFF) // Pure white
val OnSurface = Color(0xFFE5E5E5) // Off white
val OnSurfaceVariant = Color(0xFF9CA3AF) // Gray text
val OnSurfaceTertiary = Color(0xFF6B7280) // Muted gray
// Functional Colors
val Success = Color(0xFF10B981) // Green
val Error = Color(0xFFEF4444) // Red
val Warning = Color(0xFFFBBF24) // Amber
val Info = Color(0xFF3B82F6) // Blue
// Border & Dividers - Subtle
val Border = Color(0xFF2A2A2A) // Subtle border
val Divider = Color(0xFF1F1F1F) // Very subtle divider
// Overlay & Shadows
// 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)
// Category Colors - Minimalist Palette (subtle & muted)
val CategoryColors = listOf(
Pair(0xFF3B82F6L, 0xFF60A5FAL), // Blue gradient
Pair(0xFF8B5CF6L, 0xFFA78BFAL), // Purple gradient
Pair(0xFF10B981L, 0xFF34D399L), // Green gradient
Pair(0xFFF59E0BL, 0xFFFBBF24L), // Amber gradient
Pair(0xFFEF4444L, 0xFFF87171L), // Red gradient
Pair(0xFF06B6D4L, 0xFF22D3EEL), // Cyan gradient
Pair(0xFFEC4899L, 0xFFF472B6L), // Pink gradient
Pair(0xFF6366F1L, 0xFF818CF8L) // Indigo gradient
)
}
// LIGHT THEME COLORS
object LightColors {
val Background = Color(0xFFF8F9FA)
val Surface = Color(0xFFFFFFFF)
val SurfaceVariant = Color(0xFFF1F3F5)
val SurfaceElevated = Color(0xFFFFFFFF)
val Primary = Color(0xFF3B82F6)
val PrimaryVariant = Color(0xFF2563EB)
val PrimaryContainer = Color(0xFFDCEEFF)
val Secondary = Color(0xFF8B5CF6)
val SecondaryVariant = Color(0xFF7C3AED)
val OnBackground = Color(0xFF1F2937)
val OnSurface = Color(0xFF374151)
val OnSurfaceVariant = Color(0xFF6B7280)
val OnSurfaceTertiary = Color(0xFF9CA3AF)
val Success = Color(0xFF10B981)
val Error = Color(0xFFEF4444)
val Warning = Color(0xFFA16207)
val Info = Color(0xFF3B82F6)
val Border = Color(0xFFE5E7EB)
val Divider = Color(0xFFF3F4F6)
val Overlay = Color(0xFF000000).copy(alpha = 0.3f)
val Shadow = Color(0xFF000000).copy(alpha = 0.1f)
}
// Category Colors - Same for both themes
val CategoryColors = listOf(
Pair(0xFF3B82F6L, 0xFF60A5FAL), // Blue
Pair(0xFF8B5CF6L, 0xFFA78BFAL), // Purple
Pair(0xFF10B981L, 0xFF34D399L), // Green
Pair(0xFFF59E0BL, 0xFFFBBF24L), // Amber
Pair(0xFFEF4444L, 0xFFF87171L), // Red
Pair(0xFF06B6D4L, 0xFF22D3EEL), // Cyan
Pair(0xFFEC4899L, 0xFFF472B6L), // Pink
Pair(0xFF6366F1L, 0xFF818CF8L) // Indigo
)
// Animation Durations
const val ANIMATION_DURATION_SHORT = 150
const val ANIMATION_DURATION_MEDIUM = 300
@ -73,7 +88,7 @@ object Constants {
const val FADE_IN_DURATION = 200
const val FADE_OUT_DURATION = 200
// Spacing System (8dp grid)
// Spacing System
object Spacing {
const val ExtraSmall = 4
const val Small = 8
@ -100,4 +115,79 @@ object Constants {
const val Large = 8
const val ExtraLarge = 16
}
}
// REACTIVE APP COLORS - Using Compose State
object AppColors {
// Internal state
private var _isDark by mutableStateOf(true)
// Public setter
fun setTheme(isDark: Boolean) {
_isDark = isDark
}
// All colors are now reactive via mutableStateOf
val Background: Color
get() = if (_isDark) Constants.DarkColors.Background else Constants.LightColors.Background
val Surface: Color
get() = if (_isDark) Constants.DarkColors.Surface else Constants.LightColors.Surface
val SurfaceVariant: Color
get() = if (_isDark) Constants.DarkColors.SurfaceVariant else Constants.LightColors.SurfaceVariant
val SurfaceElevated: Color
get() = if (_isDark) Constants.DarkColors.SurfaceElevated else Constants.LightColors.SurfaceElevated
val Primary: Color
get() = if (_isDark) Constants.DarkColors.Primary else Constants.LightColors.Primary
val PrimaryVariant: Color
get() = if (_isDark) Constants.DarkColors.PrimaryVariant else Constants.LightColors.PrimaryVariant
val PrimaryContainer: Color
get() = if (_isDark) Constants.DarkColors.PrimaryContainer else Constants.LightColors.PrimaryContainer
val Secondary: Color
get() = if (_isDark) Constants.DarkColors.Secondary else Constants.LightColors.Secondary
val SecondaryVariant: Color
get() = if (_isDark) Constants.DarkColors.SecondaryVariant else Constants.LightColors.SecondaryVariant
val OnBackground: Color
get() = if (_isDark) Constants.DarkColors.OnBackground else Constants.LightColors.OnBackground
val OnSurface: Color
get() = if (_isDark) Constants.DarkColors.OnSurface else Constants.LightColors.OnSurface
val OnSurfaceVariant: Color
get() = if (_isDark) Constants.DarkColors.OnSurfaceVariant else Constants.LightColors.OnSurfaceVariant
val OnSurfaceTertiary: Color
get() = if (_isDark) Constants.DarkColors.OnSurfaceTertiary else Constants.LightColors.OnSurfaceTertiary
val Success: Color
get() = if (_isDark) Constants.DarkColors.Success else Constants.LightColors.Success
val Error: Color
get() = if (_isDark) Constants.DarkColors.Error else Constants.LightColors.Error
val Warning: Color
get() = if (_isDark) Constants.DarkColors.Warning else Constants.LightColors.Warning
val Info: Color
get() = if (_isDark) Constants.DarkColors.Info else Constants.LightColors.Info
val Border: Color
get() = if (_isDark) Constants.DarkColors.Border else Constants.LightColors.Border
val Divider: Color
get() = if (_isDark) Constants.DarkColors.Divider else Constants.LightColors.Divider
val Overlay: Color
get() = if (_isDark) Constants.DarkColors.Overlay else Constants.LightColors.Overlay
val Shadow: Color
get() = if (_isDark) Constants.DarkColors.Shadow else Constants.LightColors.Shadow
}

View File

@ -12,6 +12,10 @@ constraintlayout = "2.1.4"
uiText = "1.10.0"
material3 = "1.4.0"
animationCore = "1.10.0"
firebaseAnnotations = "17.0.0"
firebaseFirestoreKtx = "26.0.2"
uiGraphics = "1.10.0"
roomCompiler = "2.8.4"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -25,6 +29,10 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
androidx-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
androidx-animation-core = { group = "androidx.compose.animation", name = "animation-core", version.ref = "animationCore" }
firebase-annotations = { group = "com.google.firebase", name = "firebase-annotations", version.ref = "firebaseAnnotations" }
firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtx" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomCompiler" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }