Compare commits

..

No commits in common. "80774b58eaa6e9001c8bd04070cce0877a6e58f9" and "e541c4e234e3f3b6d25e1023f4be72a38e1493d9" have entirely different histories.

14 changed files with 810 additions and 1168 deletions

View File

@ -7,7 +7,7 @@ plugins {
android { android {
namespace = "com.example.notesai" namespace = "com.example.notesai"
compileSdk = 35 compileSdk = 34
defaultConfig { defaultConfig {
applicationId = "com.example.notesai" applicationId = "com.example.notesai"
@ -64,9 +64,6 @@ dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0") implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("com.google.ai.client.generativeai:generativeai:0.9.0") implementation("com.google.ai.client.generativeai:generativeai:0.9.0")
implementation(libs.androidx.ui.text)
implementation(libs.androidx.material3)
implementation(libs.androidx.animation.core)
// Untuk integrasi Gemini AI (optional - uncomment jika sudah ada API key) // Untuk integrasi Gemini AI (optional - uncomment jika sudah ada API key)
// implementation("com.google.ai.client.generativeai:generativeai:0.1.2") // implementation("com.google.ai.client.generativeai:generativeai:0.1.2")

View File

@ -1,11 +1,10 @@
package com.example.notesai package com.example.notesai
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -20,8 +19,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import java.util.UUID import java.util.UUID
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import com.example.notesai.data.local.DataStoreManager import com.example.notesai.data.local.DataStoreManager
import com.example.notesai.presentation.components.DrawerMenu import com.example.notesai.presentation.components.DrawerMenu
@ -40,64 +37,20 @@ import com.example.notesai.data.model.Category
import com.example.notesai.util.updateWhere import com.example.notesai.util.updateWhere
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
// File: MainActivity.kt (Bagian Theme Setup)
// Ganti MaterialTheme di setContent dengan ini:
import com.example.notesai.util.Constants
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
MaterialTheme( MaterialTheme(
colorScheme = darkColorScheme( colorScheme = darkColorScheme(
// Primary colors primary = Color(0xFF6366F1),
primary = Constants.AppColors.Primary, secondary = Color(0xFFA855F7),
background = Color(0xFF0F172A),
surface = Color(0xFF1E293B),
onPrimary = Color.White, onPrimary = Color.White,
primaryContainer = Constants.AppColors.PrimaryContainer,
onPrimaryContainer = Color.White,
// Secondary colors
secondary = Constants.AppColors.Secondary,
onSecondary = Color.White, onSecondary = Color.White,
secondaryContainer = Constants.AppColors.SecondaryVariant, onBackground = Color(0xFFE2E8F0),
onSecondaryContainer = Color.White, onSurface = Color(0xFFE2E8F0)
// Background colors
background = Constants.AppColors.Background,
onBackground = Constants.AppColors.OnBackground,
// Surface colors
surface = Constants.AppColors.Surface,
onSurface = Constants.AppColors.OnSurface,
surfaceVariant = Constants.AppColors.SurfaceVariant,
onSurfaceVariant = Constants.AppColors.OnSurfaceVariant,
// Error colors
error = Constants.AppColors.Error,
onError = Color.White,
// Other
outline = Constants.AppColors.Border,
outlineVariant = Constants.AppColors.Divider
),
typography = Typography(
// Improve typography for better readability
displayLarge = MaterialTheme.typography.displayLarge.copy(
fontWeight = FontWeight.Bold
),
headlineLarge = MaterialTheme.typography.headlineLarge.copy(
fontWeight = FontWeight.Bold
),
titleLarge = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.SemiBold
),
bodyLarge = MaterialTheme.typography.bodyLarge.copy(
lineHeight = 24.sp
),
bodyMedium = MaterialTheme.typography.bodyMedium.copy(
lineHeight = 20.sp
)
) )
) { ) {
Surface( Surface(
@ -208,12 +161,7 @@ fun NotesApp() {
floatingActionButton = { floatingActionButton = {
AnimatedVisibility( AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote, visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn( enter = scaleIn() + fadeIn(),
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
) + fadeIn(),
exit = scaleOut() + fadeOut() exit = scaleOut() + fadeOut()
) { ) {
FloatingActionButton( FloatingActionButton(
@ -225,18 +173,20 @@ fun NotesApp() {
showCategoryDialog = true showCategoryDialog = true
} }
}, },
containerColor = Constants.AppColors.Primary, containerColor = Color.Transparent,
contentColor = Color.White, modifier = Modifier
elevation = FloatingActionButtonDefaults.elevation( .shadow(8.dp, CircleShape)
defaultElevation = 8.dp, .background(
pressedElevation = 12.dp brush = Brush.linearGradient(
), colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
modifier = Modifier.size(64.dp) ),
shape = CircleShape
)
) { ) {
Icon( Icon(
Icons.Default.Add, Icons.Default.Add,
contentDescription = if (selectedCategory != null) "Tambah Catatan" else "Tambah Kategori", contentDescription = if (selectedCategory != null) "Tambah Catatan" else "Tambah Kategori",
modifier = Modifier.size(28.dp) tint = Color.White
) )
} }
} }

View File

@ -1,29 +1,43 @@
package com.example.notesai.presentation.components package com.example.notesai.presentation.components
import androidx.compose.animation.*
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.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
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.Archive
import androidx.compose.material.icons.outlined.* import androidx.compose.material.icons.filled.Create
import androidx.compose.material3.* import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.* import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.scale
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.graphics.vector.ImageVector
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 com.example.notesai.util.Constants
@Composable @Composable
fun DrawerMenu( fun DrawerMenu(
@ -31,233 +45,133 @@ fun DrawerMenu(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onItemClick: (String) -> Unit onItemClick: (String) -> Unit
) { ) {
// Backdrop with blur effect
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Constants.AppColors.Overlay) .statusBarsPadding() // Padding untuk status bar
.background(Color.Black.copy(alpha = 0.5f))
.clickable( .clickable(
onClick = onDismiss, onClick = onDismiss,
indication = null, indication = null,
interactionSource = remember { MutableInteractionSource() } interactionSource = remember { MutableInteractionSource() }
) )
) { ) {
// Drawer Content Card(
Surface(
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.width(280.dp) .width(250.dp)
.align(Alignment.CenterStart) .align(Alignment.CenterStart)
.clickable( .clickable(
onClick = {}, onClick = {},
indication = null, indication = null,
interactionSource = remember { MutableInteractionSource() } interactionSource = remember { MutableInteractionSource() }
), ),
color = Constants.AppColors.Surface, shape = RoundedCornerShape(topEnd = 0.dp, bottomEnd = 0.dp),
shadowElevation = Constants.Elevation.ExtraLarge.dp colors = CardDefaults.cardColors(
containerColor = Color(0xFF1E293B)
),
elevation = CardDefaults.cardElevation(defaultElevation = 16.dp)
) { ) {
Column( Column(modifier = Modifier.fillMaxSize()) {
modifier = Modifier.fillMaxSize() // Header Drawer dengan tombol close
) {
// Header - Minimalist
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background( .background(
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = listOf( colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
Constants.AppColors.Primary.copy(alpha = 0.15f),
Color.Transparent
)
) )
) )
.padding(Constants.Spacing.ExtraLarge.dp) .padding(24.dp)
) { ) {
Column { Row(
// App Icon with subtle background modifier = Modifier.fillMaxWidth(),
Box( horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier verticalAlignment = Alignment.CenterVertically
.size(56.dp) ) {
.background( Column {
color = Constants.AppColors.Primary.copy(alpha = 0.2f),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
),
contentAlignment = Alignment.Center
) {
Icon( Icon(
Icons.Default.Create, Icons.Default.Create,
contentDescription = null, contentDescription = null,
tint = Constants.AppColors.Primary, tint = Color.White,
modifier = Modifier.size(32.dp) modifier = Modifier.size(36.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
"AI Notes",
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
fontWeight = FontWeight.Bold
)
Text(
"Smart & Modern",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(0.8f)
) )
} }
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp)) // // Tombol Close
// IconButton(
Text( // onClick = onDismiss,
Constants.APP_NAME, // modifier = Modifier
style = MaterialTheme.typography.headlineMedium, // .size(40.dp)
color = Constants.AppColors.OnBackground, // .background(
fontWeight = FontWeight.Bold, // Color.White.copy(alpha = 0.2f),
fontSize = 24.sp // shape = CircleShape
) // )
// ) {
Spacer(modifier = Modifier.height(Constants.Spacing.ExtraSmall.dp)) // Icon(
// Icons.Default.Close,
Text( // contentDescription = "Tutup Menu",
"Smart Note Taking", // tint = Color.White,
style = MaterialTheme.typography.bodyMedium, // modifier = Modifier.size(24.dp)
color = Constants.AppColors.OnSurfaceVariant, // )
fontSize = 14.sp // }
)
} }
} }
Spacer(modifier = Modifier.height(Constants.Spacing.Large.dp)) Spacer(modifier = Modifier.height(16.dp))
// Menu Items // Menu Items
DrawerMenuItem( MenuItem(
icon = if (currentScreen == "main") Icons.Filled.Home else Icons.Outlined.Home, icon = Icons.Default.Home,
text = "Beranda", text = "Beranda",
isSelected = currentScreen == "main", isSelected = currentScreen == "main"
onClick = { onItemClick("main") } ) { onItemClick("main") }
)
DrawerMenuItem( MenuItem(
icon = if (currentScreen == "starred") Icons.Filled.Star else Icons.Outlined.StarBorder, icon = Icons.Default.Star,
text = "Berbintang", text = "Berbintang",
isSelected = currentScreen == "starred", isSelected = currentScreen == "starred"
onClick = { onItemClick("starred") } ) { onItemClick("starred") }
)
DrawerMenuItem( MenuItem(
icon = if (currentScreen == "archive") Icons.Filled.Archive else Icons.Outlined.Archive, icon = Icons.Default.Archive,
text = "Arsip", text = "Arsip",
isSelected = currentScreen == "archive", isSelected = currentScreen == "archive"
onClick = { onItemClick("archive") } ) { onItemClick("archive") }
)
DrawerMenuItem( MenuItem(
icon = if (currentScreen == "trash") Icons.Filled.Delete else Icons.Outlined.Delete, icon = Icons.Default.Delete,
text = "Sampah", text = "Sampah",
isSelected = currentScreen == "trash", isSelected = currentScreen == "trash"
onClick = { onItemClick("trash") } ) { onItemClick("trash") }
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Footer - Version info // Footer
HorizontalDivider( Divider(
color = Constants.AppColors.Divider, color = Color.White.copy(alpha = 0.1f),
modifier = Modifier.padding(horizontal = Constants.Spacing.Medium.dp) modifier = Modifier.padding(horizontal = 16.dp)
) )
Row( Text(
modifier = Modifier text = "Version 1.0.0",
.fillMaxWidth() style = MaterialTheme.typography.bodySmall,
.padding(Constants.Spacing.Medium.dp), color = Color.White.copy(alpha = 0.5f),
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.padding(16.dp)
verticalAlignment = Alignment.CenterVertically )
) {
Text(
"Version ${Constants.APP_VERSION}",
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
fontSize = 12.sp
)
// Powered by badge
Surface(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
shape = RoundedCornerShape(Constants.Radius.Small.dp)
) {
Row(
modifier = Modifier.padding(horizontal = Constants.Spacing.Small.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
Icons.Default.AutoAwesome,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(12.dp)
)
Text(
"AI",
color = Constants.AppColors.Primary,
fontSize = 11.sp,
fontWeight = FontWeight.Bold
)
}
}
}
} }
} }
} }
}
@Composable
private fun DrawerMenuItem(
icon: ImageVector,
text: String,
isSelected: Boolean,
onClick: () -> Unit
) {
// Scale animation
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.02f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
),
label = "scale"
)
Row(
modifier = Modifier
.fillMaxWidth()
.scale(scale)
.clip(RoundedCornerShape(Constants.Radius.Medium.dp))
.clickable(onClick = onClick)
.background(
color = if (isSelected)
Constants.AppColors.Primary.copy(alpha = 0.1f)
else
Color.Transparent
)
.padding(horizontal = Constants.Spacing.Medium.dp, vertical = Constants.Spacing.Medium.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Icon with background
Box(
modifier = Modifier
.size(40.dp)
.background(
color = if (isSelected)
Constants.AppColors.Primary.copy(alpha = 0.2f)
else
Constants.AppColors.SurfaceVariant,
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
icon,
contentDescription = null,
tint = if (isSelected) Constants.AppColors.Primary else Constants.AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
Spacer(modifier = Modifier.width(Constants.Spacing.Medium.dp))
// Text
Text(
text,
style = MaterialTheme.typography.bodyLarge,
color = if (isSelected) Constants.AppColors.Primary else Constants.AppColors.OnSurface,
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
fontSize = 15.sp
)
}
} }

View File

@ -1,32 +1,31 @@
package com.example.notesai.presentation.components package com.example.notesai.presentation.components
import androidx.compose.animation.*
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.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.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.AutoAwesome
import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.AutoAwesome import androidx.compose.material3.BottomAppBar
import androidx.compose.material.icons.outlined.Home import androidx.compose.material3.Icon
import androidx.compose.material.icons.outlined.StarBorder import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.* import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
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.scale
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
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 com.example.notesai.util.Constants
@Composable @Composable
fun ModernBottomBar( fun ModernBottomBar(
@ -34,125 +33,70 @@ fun ModernBottomBar(
onHomeClick: () -> Unit, onHomeClick: () -> Unit,
onAIClick: () -> Unit onAIClick: () -> Unit
) { ) {
// Floating Bottom Bar with Glassmorphism BottomAppBar(
Box( containerColor = Color.Transparent,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .shadow(8.dp, RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp))
.padding(horizontal = 16.dp) .background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0xFF1E293B).copy(0.95f),
Color(0xFF334155).copy(0.95f)
)
),
shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)
)
) { ) {
Surface( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.shadow( .padding(horizontal = 16.dp),
elevation = Constants.Elevation.Large.dp, horizontalArrangement = Arrangement.SpaceEvenly,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp) verticalAlignment = Alignment.CenterVertically
),
color = Constants.AppColors.SurfaceElevated,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
) { ) {
Row( Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .weight(1f)
.padding(vertical = 8.dp, horizontal = 24.dp), .clickable(onClick = onHomeClick)
horizontalArrangement = Arrangement.SpaceEvenly, .padding(vertical = 8.dp)
verticalAlignment = Alignment.CenterVertically
) { ) {
// Home Button Icon(
BottomBarItem( Icons.Default.Home,
selected = currentScreen == "main", contentDescription = "Home",
onClick = onHomeClick, tint = if (currentScreen == "main") Color(0xFF6366F1) else Color(0xFF94A3B8),
icon = if (currentScreen == "main") Icons.Filled.Home else Icons.Outlined.Home, modifier = Modifier.size(24.dp)
label = "Beranda"
) )
Spacer(modifier = Modifier.height(4.dp))
// AI Button Text(
BottomBarItem( "Home",
selected = currentScreen == "ai", color = if (currentScreen == "main") Color(0xFF6366F1) else Color(0xFF94A3B8),
onClick = onAIClick, style = MaterialTheme.typography.bodySmall,
icon = if (currentScreen == "ai") Icons.Filled.AutoAwesome else Icons.Outlined.AutoAwesome, fontWeight = if (currentScreen == "main") FontWeight.Bold else FontWeight.Normal
label = "AI Helper"
)
}
}
}
}
@Composable
private fun BottomBarItem(
selected: Boolean,
onClick: () -> Unit,
icon: androidx.compose.ui.graphics.vector.ImageVector,
label: String
) {
// Scale animation
val scale by animateFloatAsState(
targetValue = if (selected) 1.1f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "scale"
)
// Color animation
val iconColor by animateColorAsState(
targetValue = if (selected) Constants.AppColors.Primary else Constants.AppColors.OnSurfaceVariant,
animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM),
label = "color"
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.clickable(
onClick = onClick,
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
.padding(horizontal = 24.dp, vertical = 8.dp)
) {
// Icon with background indicator
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(48.dp)
.scale(scale)
) {
// Background indicator
if (selected) {
Box(
modifier = Modifier
.size(40.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.15f),
shape = CircleShape
)
) )
} }
Icon( Column(
icon, horizontalAlignment = Alignment.CenterHorizontally,
contentDescription = label, modifier = Modifier
tint = iconColor, .weight(1f)
modifier = Modifier.size(24.dp) .clickable(onClick = onAIClick)
) .padding(vertical = 8.dp)
} ) {
Icon(
// Label with fade animation Icons.Default.Star,
AnimatedVisibility( contentDescription = "AI Helper",
visible = selected, tint = if (currentScreen == "ai") Color(0xFFFBBF24) else Color(0xFF94A3B8),
enter = fadeIn() + expandVertically(), modifier = Modifier.size(24.dp)
exit = fadeOut() + shrinkVertically() )
) { Spacer(modifier = Modifier.height(4.dp))
Spacer(modifier = Modifier.height(4.dp)) Text(
Text( "AI Helper",
label, color = if (currentScreen == "ai") Color(0xFFFBBF24) else Color(0xFF94A3B8),
color = Constants.AppColors.Primary, style = MaterialTheme.typography.bodySmall,
style = MaterialTheme.typography.bodySmall, fontWeight = if (currentScreen == "ai") FontWeight.Bold else FontWeight.Normal
fontWeight = FontWeight.Bold, )
fontSize = 12.sp }
)
} }
} }
} }

View File

@ -1,25 +1,42 @@
package com.example.notesai.presentation.components package com.example.notesai.presentation.components
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.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.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.* import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.shadow import androidx.compose.ui.draw.shadow
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.Constants
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -33,116 +50,58 @@ fun ModernTopBar(
onSearchQueryChange: (String) -> Unit, onSearchQueryChange: (String) -> Unit,
showSearch: Boolean showSearch: Boolean
) { ) {
// Smooth transition for search bar TopAppBar(
AnimatedContent( title = {
targetState = showSearch, if (showSearch) {
transitionSpec = { TextField(
fadeIn(animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM)) togetherWith value = searchQuery,
fadeOut(animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM)) onValueChange = onSearchQueryChange,
placeholder = { Text("Cari catatan...", color = Color.White.copy(0.6f)) },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color.White,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
modifier = Modifier.fillMaxWidth()
)
} else {
Text(
title,
fontWeight = FontWeight.Bold,
fontSize = 22.sp
)
}
}, },
label = "topbar" navigationIcon = {
) { isSearching -> IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) {
if (isSearching) { Icon(
// Search Mode - Minimalist if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
Surface( contentDescription = null,
modifier = Modifier tint = Color.White
.fillMaxWidth() )
.shadow(Constants.Elevation.Small.dp),
color = Constants.AppColors.Surface
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Medium.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Search TextField
TextField(
value = searchQuery,
onValueChange = onSearchQueryChange,
placeholder = {
Text(
"Cari catatan atau kategori...",
color = Constants.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,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
modifier = Modifier
.weight(1f)
.heightIn(min = 48.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
singleLine = true,
textStyle = LocalTextStyle.current.copy(fontSize = 15.sp)
)
Spacer(modifier = Modifier.width(Constants.Spacing.Small.dp))
// Close Search Button
IconButton(
onClick = {
onSearchQueryChange("")
onSearchClick()
}
) {
Icon(
Icons.Default.Close,
contentDescription = "Close Search",
tint = Constants.AppColors.OnSurfaceVariant
)
}
}
} }
} else { },
// Normal Mode - Minimalist actions = {
Surface( IconButton(onClick = onSearchClick) {
modifier = Modifier Icon(
.fillMaxWidth() if (showSearch) Icons.Default.Close else Icons.Default.Search,
.shadow(Constants.Elevation.Small.dp), contentDescription = "Search",
color = Constants.AppColors.Surface tint = Color.White
) { )
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Small.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Back/Menu Button
IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) {
Icon(
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
contentDescription = if (showBackButton) "Back" else "Menu",
tint = Constants.AppColors.OnSurface
)
}
// Title
Text(
title,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = Constants.AppColors.OnBackground,
modifier = Modifier.weight(1f)
)
// Search Button
IconButton(onClick = onSearchClick) {
Icon(
Icons.Default.Search,
contentDescription = "Search",
tint = Constants.AppColors.OnSurfaceVariant
)
}
}
} }
} },
} colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent
),
modifier = Modifier
.background(
brush = Brush.horizontalGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
)
)
)
} }

View File

@ -1,41 +1,71 @@
package com.example.notesai.presentation.screens.ai package com.example.notesai.presentation.screens.ai
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.* import androidx.compose.material.icons.filled.Folder
import androidx.compose.runtime.* import androidx.compose.material.icons.filled.Send
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.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.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.Category
import com.example.notesai.data.model.ChatMessage
import com.example.notesai.data.model.Note import com.example.notesai.data.model.Note
import com.example.notesai.util.Constants import com.example.notesai.data.model.ChatMessage
import com.example.notesai.data.model.Category
import com.example.notesai.config.APIKey
import com.example.notesai.presentation.screens.ai.components.ChatBubble
import com.example.notesai.presentation.screens.ai.components.CompactStatItem
import com.example.notesai.presentation.screens.ai.components.SuggestionChip
import com.example.notesai.util.Constants.AppColors.Divider
import com.google.ai.client.generativeai.GenerativeModel import com.google.ai.client.generativeai.GenerativeModel
import com.google.ai.client.generativeai.type.generationConfig import com.google.ai.client.generativeai.type.generationConfig
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import kotlin.collections.plus
import java.util.*
import com.example.notesai.presentation.screens.ai.components.ChatBubble
import com.example.notesai.presentation.screens.ai.components.CompactStatItem
import com.example.notesai.presentation.screens.ai.components.SuggestionChip
import com.example.notesai.presentation.screens.ai.components.StatItem
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -56,11 +86,11 @@ fun AIHelperScreen(
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
// Inisialisasi Gemini Model // Inisialisasi Gemini Model
val generativeModel = remember { val generativeModel = remember {
GenerativeModel( GenerativeModel(
modelName = "gemini-2.0-flash-exp", modelName = "gemini-2.5-flash",
apiKey = com.example.notesai.config.APIKey.GEMINI_API_KEY, apiKey = APIKey.GEMINI_API_KEY,
generationConfig = generationConfig { generationConfig = generationConfig {
temperature = 0.8f temperature = 0.8f
topK = 40 topK = 40
@ -80,197 +110,217 @@ fun AIHelperScreen(
} }
Column( Column(
modifier = Modifier modifier = Modifier.Companion
.fillMaxSize() .fillMaxSize()
.background(Constants.AppColors.Background) .background(MaterialTheme.colorScheme.background)
) { ) {
// Category Selector & Stats - Compact // Header
Surface( Card(
color = Constants.AppColors.Surface, modifier = Modifier.Companion.fillMaxWidth(),
shadowElevation = 2.dp colors = CardDefaults.cardColors(containerColor = Color.Companion.Transparent),
shape = RoundedCornerShape(0.dp)
) { ) {
Column( Box(
modifier = Modifier modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .background(
) { brush = Brush.Companion.linearGradient(
// Category Selector colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
Box {
Card(
onClick = { showCategoryDropdown = !showCategoryDropdown },
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant
),
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(20.dp)
)
Text(
selectedCategory?.name ?: "Semua Kategori",
color = Constants.AppColors.OnSurface,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
}
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
tint = Constants.AppColors.OnSurfaceVariant
)
}
}
DropdownMenu(
expanded = showCategoryDropdown,
onDismissRequest = { showCategoryDropdown = false },
modifier = Modifier
.fillMaxWidth(0.9f)
.background(Constants.AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = { Text("Semua Kategori", color = Constants.AppColors.OnSurface) },
onClick = {
selectedCategory = null
showCategoryDropdown = false
}
) )
categories.forEach { category -> )
DropdownMenuItem( .padding(20.dp)
text = { Text(category.name, color = Constants.AppColors.OnSurface) }, ) {
onClick = { Column {
selectedCategory = category Row(verticalAlignment = Alignment.Companion.CenterVertically) {
showCategoryDropdown = false Icon(
} Icons.Default.Star,
contentDescription = null,
tint = Color(0xFFFBBF24),
modifier = Modifier.Companion.size(28.dp)
)
Spacer(modifier = Modifier.Companion.width(12.dp))
Column {
Text(
"AI Helper",
style = MaterialTheme.typography.titleLarge,
color = Color.Companion.White,
fontWeight = FontWeight.Companion.Bold
)
Text(
"Powered by Gemini AI",
style = MaterialTheme.typography.bodySmall,
color = Color.Companion.White.copy(0.8f)
) )
} }
} }
} }
Spacer(modifier = Modifier.height(12.dp))
// Stats - Compact
val filteredNotes = if (selectedCategory != null) {
notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived }
} else {
notes.filter { !it.isArchived }
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
CompactStatItem(
icon = Icons.Default.Description,
value = filteredNotes.size.toString(),
label = "Catatan"
)
CompactStatItem(
icon = Icons.Default.Star,
value = filteredNotes.count { it.isPinned }.toString(),
label = "Dipasang"
)
CompactStatItem(
icon = Icons.Default.Folder,
value = categories.size.toString(),
label = "Kategori"
)
}
} }
} }
HorizontalDivider(color = Constants.AppColors.Divider) // Category Selector & Stats - Compact Version
Column(
modifier = Modifier.Companion
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
) {
// Category Selector
Box {
Card(
modifier = Modifier.Companion
.fillMaxWidth()
.clickable { showCategoryDropdown = !showCategoryDropdown },
colors = CardDefaults.cardColors(containerColor = Color(0xFF1E293B)),
shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier.Companion
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Companion.CenterVertically
) {
Row(verticalAlignment = Alignment.Companion.CenterVertically) {
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Color(0xFF6366F1),
modifier = Modifier.Companion.size(20.dp)
)
Spacer(modifier = Modifier.Companion.width(8.dp))
Text(
selectedCategory?.name ?: "Semua Kategori",
color = Color.Companion.White,
style = MaterialTheme.typography.bodyMedium
)
}
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
tint = Color(0xFF94A3B8)
)
}
}
DropdownMenu(
expanded = showCategoryDropdown,
onDismissRequest = { showCategoryDropdown = false },
modifier = Modifier.Companion
.fillMaxWidth()
.background(Color(0xFF1E293B))
) {
DropdownMenuItem(
text = { Text("Semua Kategori", color = Color.Companion.White) },
onClick = {
selectedCategory = null
showCategoryDropdown = false
}
)
categories.forEach { category ->
DropdownMenuItem(
text = { Text(category.name, color = Color.Companion.White) },
onClick = {
selectedCategory = category
showCategoryDropdown = false
}
)
}
}
}
// Stats - Compact
Spacer(modifier = Modifier.Companion.height(12.dp))
val filteredNotes = if (selectedCategory != null) {
notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived }
} else {
notes.filter { !it.isArchived }
}
Row(
modifier = Modifier.Companion.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
CompactStatItem(
label = "Total",
value = filteredNotes.size.toString(),
color = Color(0xFF6366F1)
)
CompactStatItem(
label = "Dipasang",
value = filteredNotes.count { it.isPinned }.toString(),
color = Color(0xFFFBBF24)
)
CompactStatItem(
label = "Kategori",
value = categories.size.toString(),
color = Color(0xFFA855F7)
)
}
}
Divider(color = Color(0xFF334155), thickness = 1.dp)
// Chat Area // Chat Area
Box( Column(
modifier = Modifier modifier = Modifier.Companion
.weight(1f) .weight(1f)
.fillMaxWidth() .fillMaxWidth()
) { ) {
if (chatMessages.isEmpty()) { if (chatMessages.isEmpty()) {
// Welcome State // Welcome State
Column( Column(
modifier = Modifier modifier = Modifier.Companion
.fillMaxSize() .fillMaxSize()
.padding(32.dp), .padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.Companion.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Box( Icon(
modifier = Modifier Icons.Default.Star,
.size(80.dp) contentDescription = null,
.background( modifier = Modifier.Companion.size(64.dp),
color = Constants.AppColors.Primary.copy(alpha = 0.1f), tint = Color(0xFF6366F1).copy(0.5f)
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.AutoAwesome,
contentDescription = null,
modifier = Modifier.size(40.dp),
tint = Constants.AppColors.Primary
)
}
Spacer(modifier = Modifier.height(24.dp))
Text(
"AI Assistant",
style = MaterialTheme.typography.headlineMedium,
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold
) )
Spacer(modifier = Modifier.Companion.height(16.dp))
Spacer(modifier = Modifier.height(8.dp)) Text(
"Mulai Percakapan",
style = MaterialTheme.typography.titleLarge,
color = Color.Companion.White,
fontWeight = FontWeight.Companion.Bold
)
Spacer(modifier = Modifier.Companion.height(8.dp))
Text( Text(
"Tanyakan apa saja tentang catatan Anda", "Tanyakan apa saja tentang catatan Anda",
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurfaceVariant, color = Color(0xFF94A3B8),
textAlign = TextAlign.Center textAlign = TextAlign.Companion.Center
) )
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.Companion.height(24.dp))
// Suggestion Chips // Suggestion Chips
Column( Column(
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Companion.Start,
verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.Companion.fillMaxWidth(0.8f)
modifier = Modifier.fillMaxWidth(0.85f)
) { ) {
Text( Text(
"Contoh pertanyaan:", "Contoh pertanyaan:",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary color = Color(0xFF64748B),
modifier = Modifier.Companion.padding(bottom = 8.dp)
) )
SuggestionChip("Analisis catatan saya") { prompt = it } SuggestionChip("Analisis catatan saya", onSelect = { prompt = it })
SuggestionChip("Buat ringkasan") { prompt = it } SuggestionChip("Buat ringkasan", onSelect = { prompt = it })
SuggestionChip("Berikan saran organisasi") { prompt = it } SuggestionChip("Berikan saran organisasi", onSelect = { prompt = it })
} }
} }
} else { } else {
// Chat Messages // Chat Messages
Column( Column(
modifier = Modifier modifier = Modifier.Companion
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState) .verticalScroll(scrollState)
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 100.dp) .padding(16.dp)
) { ) {
chatMessages.forEach { message -> chatMessages.forEach { message ->
ChatBubble( ChatBubble(
@ -286,34 +336,36 @@ fun AIHelperScreen(
}, },
showCopied = showCopiedMessage && copiedMessageId == message.id showCopied = showCopiedMessage && copiedMessageId == message.id
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.Companion.height(12.dp))
} }
// Loading Indicator // Loading Indicator
if (isLoading) { if (isLoading) {
Row( Row(
modifier = Modifier modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp), .padding(vertical = 8.dp),
horizontalArrangement = Arrangement.Start horizontalArrangement = Arrangement.Start
) { ) {
Surface( Card(
color = Constants.AppColors.SurfaceVariant, colors = CardDefaults.cardColors(
shape = RoundedCornerShape(16.dp) containerColor = Color(0xFF1E293B)
),
shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp)
) { ) {
Row( Row(
modifier = Modifier.padding(16.dp), modifier = Modifier.Companion.padding(16.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.Companion.CenterVertically
horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier.size(20.dp), modifier = Modifier.Companion.size(20.dp),
color = Constants.AppColors.Primary, color = Color(0xFF6366F1),
strokeWidth = 2.dp strokeWidth = 2.dp
) )
Spacer(modifier = Modifier.Companion.width(12.dp))
Text( Text(
"AI sedang berpikir...", "AI sedang berpikir...",
color = Constants.AppColors.OnSurfaceVariant, color = Color(0xFF94A3B8),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
} }
@ -323,46 +375,52 @@ fun AIHelperScreen(
// Error Message // Error Message
if (errorMessage.isNotEmpty()) { if (errorMessage.isNotEmpty()) {
Surface( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.Companion.fillMaxWidth(),
color = Constants.AppColors.Error.copy(alpha = 0.1f), colors = CardDefaults.cardColors(
shape = RoundedCornerShape(12.dp) containerColor = Color(0xFFEF4444).copy(0.2f)
),
shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
) { ) {
Row( Row(
modifier = Modifier.padding(12.dp), modifier = Modifier.Companion.padding(12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.Companion.CenterVertically
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Icon( Icon(
Icons.Default.Warning, Icons.Default.Warning,
contentDescription = null, contentDescription = null,
tint = Constants.AppColors.Error, tint = Color(0xFFEF4444),
modifier = Modifier.size(20.dp) modifier = Modifier.Companion.size(20.dp)
) )
Spacer(modifier = Modifier.Companion.width(8.dp))
Text( Text(
errorMessage, errorMessage,
color = Constants.AppColors.Error, color = Color(0xFFEF4444),
style = MaterialTheme.typography.bodySmall style = MaterialTheme.typography.bodySmall
) )
} }
} }
} }
Spacer(modifier = Modifier.Companion.height(80.dp))
} }
} }
} }
// Input Area - Minimalist // Input Area
Surface( Card(
color = Constants.AppColors.Surface, modifier = Modifier.Companion.fillMaxWidth(),
shadowElevation = 8.dp, colors = CardDefaults.cardColors(
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) containerColor = Color(0xFF1E293B)
),
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp), .padding(16.dp),
verticalAlignment = Alignment.Bottom, verticalAlignment = Alignment.Companion.Bottom
horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
OutlinedTextField( OutlinedTextField(
value = prompt, value = prompt,
@ -370,30 +428,33 @@ fun AIHelperScreen(
placeholder = { placeholder = {
Text( Text(
"Ketik pesan...", "Ketik pesan...",
color = Constants.AppColors.OnSurfaceTertiary color = Color(0xFF64748B)
) )
}, },
modifier = Modifier modifier = Modifier.Companion
.weight(1f) .weight(1f)
.heightIn(min = 48.dp, max = 120.dp), .heightIn(min = 48.dp, max = 120.dp),
colors = OutlinedTextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground, focusedTextColor = Color.Companion.White,
unfocusedTextColor = Constants.AppColors.OnSurface, unfocusedTextColor = Color.Companion.White,
focusedContainerColor = Constants.AppColors.SurfaceVariant, focusedContainerColor = Color(0xFF334155),
unfocusedContainerColor = Constants.AppColors.SurfaceVariant, unfocusedContainerColor = Color(0xFF334155),
cursorColor = Constants.AppColors.Primary, cursorColor = Color(0xFFA855F7),
focusedBorderColor = Constants.AppColors.Primary, focusedIndicatorColor = Color(0xFF6366F1),
unfocusedBorderColor = Color.Transparent unfocusedIndicatorColor = Color(0xFF475569)
), ),
shape = RoundedCornerShape(24.dp), shape = androidx.compose.foundation.shape.RoundedCornerShape(24.dp),
maxLines = 4 maxLines = 4
) )
Spacer(modifier = Modifier.Companion.width(12.dp))
// Send Button // Send Button
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
if (prompt.isNotBlank() && !isLoading) { if (prompt.isNotBlank() && !isLoading) {
scope.launch { scope.launch {
// Add user message
chatMessages = chatMessages + ChatMessage( chatMessages = chatMessages + ChatMessage(
message = prompt, message = prompt,
isUser = true isUser = true
@ -424,14 +485,18 @@ fun AIHelperScreen(
} }
} }
val fullPrompt = "$notesContext\n\nPertanyaan: $userPrompt\n\nBerikan jawaban dalam bahasa Indonesia yang natural dan membantu." val fullPrompt =
"$notesContext\n\nPertanyaan: $userPrompt\n\nBerikan jawaban dalam bahasa Indonesia yang natural dan membantu."
val result = generativeModel.generateContent(fullPrompt) val result = generativeModel.generateContent(fullPrompt)
val response = result.text ?: "Tidak ada respons dari AI" val response = result.text ?: "Tidak ada respons dari AI"
// Add AI response
chatMessages = chatMessages + ChatMessage( chatMessages = chatMessages + ChatMessage(
message = response, message = response,
isUser = false isUser = false
) )
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "Error: ${e.message}" errorMessage = "Error: ${e.message}"
} finally { } finally {
@ -440,14 +505,21 @@ fun AIHelperScreen(
} }
} }
}, },
containerColor = Constants.AppColors.Primary, containerColor = Color.Companion.Transparent,
modifier = Modifier.size(48.dp) modifier = Modifier.Companion
.size(48.dp)
.background(
brush = Brush.Companion.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
),
shape = CircleShape
)
) { ) {
Icon( Icon(
Icons.Default.Send, Icons.Default.Send,
contentDescription = "Send", contentDescription = "Send",
tint = Color.White, tint = Color.Companion.White,
modifier = Modifier.size(20.dp) modifier = Modifier.Companion.size(24.dp)
) )
} }
} }

View File

@ -1,16 +1,11 @@
package com.example.notesai.presentation.screens.ai.components package com.example.notesai.presentation.screens.ai.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoAwesome import androidx.compose.material.icons.filled.AutoAwesome
@ -20,7 +15,6 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -30,7 +24,6 @@ import androidx.compose.ui.graphics.Color
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.data.model.ChatMessage import com.example.notesai.data.model.ChatMessage
import com.example.notesai.util.Constants
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@ -48,34 +41,28 @@ fun ChatBubble(
horizontalArrangement = if (message.isUser) Arrangement.End else Arrangement.Start horizontalArrangement = if (message.isUser) Arrangement.End else Arrangement.Start
) { ) {
if (!message.isUser) { if (!message.isUser) {
Box( // Ganti ikon bintang dengan ikon robot/sparkles
Icon(
Icons.Default.AutoAwesome, // Atau bisa diganti dengan ikon lain seperti AutoAwesome
contentDescription = null,
tint = Color(0xFF6366F1), // Warna ungu/biru untuk AI
modifier = Modifier modifier = Modifier
.size(32.dp) .size(32.dp)
.background( .padding(end = 8.dp)
color = Constants.AppColors.Primary.copy(alpha = 0.1f), )
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.AutoAwesome,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(16.dp)
)
}
Spacer(modifier = Modifier.width(8.dp))
} }
Column( Column(
modifier = Modifier.fillMaxWidth(0.85f), modifier = Modifier.fillMaxWidth(0.85f),
horizontalAlignment = if (message.isUser) Alignment.End else Alignment.Start horizontalAlignment = if (message.isUser) Alignment.End else Alignment.Start
) { ) {
Surface( Card(
color = if (message.isUser) colors = CardDefaults.cardColors(
Constants.AppColors.Primary containerColor = if (message.isUser)
else Color(0xFF6366F1)
Constants.AppColors.SurfaceVariant, else
Color(0xFF1E293B)
),
shape = RoundedCornerShape( shape = RoundedCornerShape(
topStart = 16.dp, topStart = 16.dp,
topEnd = 16.dp, topEnd = 16.dp,
@ -86,7 +73,7 @@ fun ChatBubble(
Column(modifier = Modifier.padding(12.dp)) { Column(modifier = Modifier.padding(12.dp)) {
Text( Text(
message.message, message.message,
color = if (message.isUser) Color.White else Constants.AppColors.OnSurface, color = Color.White,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
lineHeight = 20.sp lineHeight = 20.sp
) )
@ -98,25 +85,21 @@ fun ChatBubble(
) { ) {
Text( Text(
dateFormat.format(Date(message.timestamp)), dateFormat.format(Date(message.timestamp)),
color = if (message.isUser) color = Color.White.copy(0.6f),
Color.White.copy(0.7f)
else
Constants.AppColors.OnSurfaceTertiary,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
fontSize = 11.sp,
modifier = Modifier.padding(top = 4.dp) modifier = Modifier.padding(top = 4.dp)
) )
if (!message.isUser) { if (!message.isUser) {
IconButton( IconButton(
onClick = onCopy, onClick = onCopy,
modifier = Modifier.size(28.dp) modifier = Modifier.size(32.dp)
) { ) {
Icon( Icon(
Icons.Default.ContentCopy, Icons.Default.ContentCopy,
contentDescription = "Copy", contentDescription = "Copy",
tint = Constants.AppColors.OnSurfaceVariant, tint = Color.White.copy(0.7f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(16.dp)
) )
} }
} }
@ -127,9 +110,8 @@ fun ChatBubble(
if (showCopied && !message.isUser) { if (showCopied && !message.isUser) {
Text( Text(
"✓ Disalin", "✓ Disalin",
color = Constants.AppColors.Success, color = Color(0xFF10B981),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
fontSize = 11.sp,
modifier = Modifier.padding(top = 4.dp, start = 8.dp) modifier = Modifier.padding(top = 4.dp, start = 8.dp)
) )
} }

View File

@ -1,15 +1,11 @@
package com.example.notesai.presentation.screens.ai.components package com.example.notesai.presentation.screens.ai.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -18,45 +14,29 @@ import androidx.compose.ui.Modifier
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 com.example.notesai.util.Constants
@Composable @Composable
fun CompactStatItem( fun CompactStatItem(label: String, value: String, color: Color) {
icon: androidx.compose.ui.graphics.vector.ImageVector,
value: String,
label: String
) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier modifier = Modifier
.background( .background(
color = Constants.AppColors.SurfaceVariant, color = Color(0xFF1E293B),
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
.padding(horizontal = 12.dp, vertical = 8.dp) .padding(horizontal = 12.dp, vertical = 8.dp)
) { ) {
Icon( Text(
icon, value,
contentDescription = null, style = MaterialTheme.typography.titleMedium,
tint = Constants.AppColors.Primary, color = color,
modifier = Modifier.size(16.dp) fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.width(4.dp))
Text(
label,
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF94A3B8)
) )
Column {
Text(
value,
style = MaterialTheme.typography.titleMedium,
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
Text(
label,
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
fontSize = 11.sp
)
}
} }
} }

View File

@ -1,7 +1,6 @@
package com.example.notesai.presentation.screens.ai.components package com.example.notesai.presentation.screens.ai.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -9,48 +8,44 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
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.Lightbulb
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.Constants
@Composable @Composable
fun SuggestionChip( fun SuggestionChip(text: String, onSelect: (String) -> Unit) {
text: String, Card(
onSelect: (String) -> Unit modifier = Modifier
) { .padding(vertical = 4.dp)
Surface( .clickable { onSelect(text) },
onClick = { onSelect(text) }, colors = CardDefaults.cardColors(
color = Constants.AppColors.SurfaceVariant, containerColor = Color(0xFF1E293B)
),
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(12.dp)
) { ) {
Row( Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Icon( Icon(
Icons.Default.Lightbulb, Icons.Default.Star,
contentDescription = null, contentDescription = null,
tint = Constants.AppColors.Primary, tint = Color(0xFF6366F1),
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text, text,
color = Constants.AppColors.OnSurface, color = Color.White,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium
fontSize = 14.sp
) )
} }
} }

View File

@ -1,3 +1,4 @@
// File: presentation/screens/main/MainScreen.kt
package com.example.notesai.presentation.screens.main package com.example.notesai.presentation.screens.main
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -27,7 +28,7 @@ fun MainScreen(
onNoteClick: (Note) -> Unit, onNoteClick: (Note) -> Unit,
onPinToggle: (Note) -> Unit, onPinToggle: (Note) -> Unit,
onCategoryDelete: (Category) -> Unit, onCategoryDelete: (Category) -> Unit,
onCategoryEdit: (Category, String, Long, Long) -> Unit onCategoryEdit: (Category, String, Long, Long) -> Unit // Parameter baru
) { ) {
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
if (selectedCategory == null) { if (selectedCategory == null) {
@ -57,12 +58,7 @@ fun MainScreen(
} else { } else {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2), columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues( contentPadding = PaddingValues(16.dp),
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 100.dp // Extra space untuk floating bottom bar
),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalItemSpacing = 12.dp, verticalItemSpacing = 12.dp,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
@ -106,12 +102,7 @@ fun MainScreen(
} else { } else {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2), columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues( contentPadding = PaddingValues(16.dp),
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 100.dp // Extra space untuk floating bottom bar
),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalItemSpacing = 12.dp, verticalItemSpacing = 12.dp,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()

View File

@ -1,28 +1,21 @@
// File: presentation/screens/main/components/CategoryCard.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.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.* import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.FolderOpen
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.draw.scale
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 com.example.notesai.data.model.Category import com.example.notesai.data.model.Category
import com.example.notesai.util.Constants
@Composable @Composable
fun CategoryCard( fun CategoryCard(
@ -35,33 +28,16 @@ fun CategoryCard(
var showDeleteConfirm by remember { mutableStateOf(false) } var showDeleteConfirm by remember { mutableStateOf(false) }
var showEditDialog by remember { mutableStateOf(false) } var showEditDialog by remember { mutableStateOf(false) }
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
var isPressed by remember { mutableStateOf(false) }
// Smooth scale animation
val scale by animateFloatAsState(
targetValue = if (isPressed) 0.95f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "scale"
)
// Delete Confirmation Dialog // Delete Confirmation Dialog
if (showDeleteConfirm) { if (showDeleteConfirm) {
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteConfirm = false }, onDismissRequest = { showDeleteConfirm = false },
title = { title = { Text("Pindahkan ke Sampah?", color = Color.White) },
Text(
"Pindahkan ke Sampah?",
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
},
text = { text = {
Text( Text(
"Kategori '${category.name}' dan $noteCount catatan di dalamnya akan dipindahkan ke sampah.", "Kategori '${category.name}' dan semua catatan di dalamnya akan dipindahkan ke sampah.",
color = Constants.AppColors.OnSurfaceVariant color = Color.White
) )
}, },
confirmButton = { confirmButton = {
@ -71,19 +47,23 @@ fun CategoryCard(
showDeleteConfirm = false showDeleteConfirm = false
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Error containerColor = Color(0xFFEF4444)
) )
) { ) {
Text("Hapus", color = Color.White) Text("Hapus", color = Color.White)
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDeleteConfirm = false }) { Button(
Text("Batal", color = Constants.AppColors.OnSurfaceVariant) onClick = { showDeleteConfirm = false },
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF64748B)
)
) {
Text("Batal", color = Color.White)
} }
}, },
containerColor = Constants.AppColors.Surface, containerColor = Color(0xFF1E293B)
shape = RoundedCornerShape(Constants.Radius.Large.dp)
) )
} }
@ -99,19 +79,13 @@ fun CategoryCard(
) )
} }
// Main Card - Minimalist Design
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.scale(scale)
.clickable(onClick = onClick), .clickable(onClick = onClick),
shape = RoundedCornerShape(Constants.Radius.Large.dp), shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(containerColor = Color.Transparent),
containerColor = Constants.AppColors.Surface elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
),
elevation = CardDefaults.cardElevation(
defaultElevation = Constants.Elevation.Small.dp
)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@ -119,146 +93,94 @@ fun CategoryCard(
.background( .background(
brush = Brush.linearGradient( brush = Brush.linearGradient(
colors = listOf( colors = listOf(
Color(category.gradientStart).copy(alpha = 0.1f), Color(category.gradientStart),
Color(category.gradientEnd).copy(alpha = 0.05f) Color(category.gradientEnd)
) )
) )
) )
.padding(20.dp)
) { ) {
Column( Column {
modifier = Modifier Icon(
.fillMaxWidth() Icons.Default.Folder,
.padding(Constants.Spacing.Large.dp) contentDescription = null,
) { tint = Color.White.copy(0.9f),
Row( modifier = Modifier.size(32.dp)
modifier = Modifier.fillMaxWidth(), )
horizontalArrangement = Arrangement.SpaceBetween, Spacer(modifier = Modifier.height(12.dp))
verticalAlignment = Alignment.Top
) {
// Icon dengan gradient accent
Box(
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(Constants.Radius.Medium.dp))
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(category.gradientStart).copy(alpha = 0.2f),
Color(category.gradientEnd).copy(alpha = 0.1f)
)
)
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Outlined.FolderOpen,
contentDescription = null,
tint = Color(category.gradientStart),
modifier = Modifier.size(24.dp)
)
}
// Menu Button
Box {
IconButton(
onClick = { showMenu = true },
modifier = Modifier.size(32.dp)
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Constants.AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(Constants.AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Kategori",
color = Constants.AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showEditDialog = true
}
)
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = Constants.AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = Constants.AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
)
}
}
}
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
// Category Name
Text( Text(
category.name, category.name,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Constants.AppColors.OnBackground, color = Color.White
fontSize = 18.sp
) )
Spacer(modifier = Modifier.height(4.dp))
Text(
"$noteCount catatan",
style = MaterialTheme.typography.bodySmall,
color = Color.White.copy(0.8f)
)
}
Spacer(modifier = Modifier.height(Constants.Spacing.ExtraSmall.dp)) // Menu Button (Titik Tiga)
Box(
// Note Count dengan subtle styling modifier = Modifier.align(Alignment.TopEnd)
Row( ) {
verticalAlignment = Alignment.CenterVertically, IconButton(
horizontalArrangement = Arrangement.spacedBy(4.dp) onClick = { showMenu = true }
) { ) {
Icon( Icon(
Icons.Default.Description, Icons.Default.MoreVert,
contentDescription = null, contentDescription = "Menu",
tint = Constants.AppColors.OnSurfaceTertiary, tint = Color.White.copy(0.9f)
modifier = Modifier.size(14.dp)
) )
Text( }
"$noteCount catatan",
style = MaterialTheme.typography.bodySmall, DropdownMenu(
color = Constants.AppColors.OnSurfaceTertiary, expanded = showMenu,
fontSize = 13.sp onDismissRequest = { showMenu = false },
modifier = Modifier.background(Color(0xFF1E293B))
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = Color(0xFF6366F1),
modifier = Modifier.size(20.dp)
)
Text("Edit Kategori", color = Color.White)
}
},
onClick = {
showMenu = false
showEditDialog = true
}
)
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = Color(0xFFEF4444),
modifier = Modifier.size(20.dp)
)
Text("Pindah ke Sampah", color = Color.White)
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
) )
} }
} }
@ -272,23 +194,31 @@ fun EditCategoryDialog(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSave: (String, Long, Long) -> Unit onSave: (String, Long, Long) -> Unit
) { ) {
val gradients = listOf(
Pair(0xFF6366F1L, 0xFFA855F7L),
Pair(0xFFEC4899L, 0xFFF59E0BL),
Pair(0xFF8B5CF6L, 0xFFEC4899L),
Pair(0xFF06B6D4L, 0xFF3B82F6L),
Pair(0xFF10B981L, 0xFF059669L),
Pair(0xFFF59E0BL, 0xFFEF4444L),
Pair(0xFF6366F1L, 0xFF8B5CF6L),
Pair(0xFFEF4444L, 0xFFDC2626L)
)
var name by remember { mutableStateOf(category.name) } var name by remember { mutableStateOf(category.name) }
var selectedGradient by remember { var selectedGradient by remember {
mutableStateOf( mutableStateOf(
Constants.AppColors.CategoryColors.indexOfFirst { gradients.indexOfFirst {
it.first == category.gradientStart && it.second == category.gradientEnd it.first == category.gradientStart && it.second == category.gradientEnd
}.takeIf { it >= 0 } ?: 0 }.takeIf { it >= 0 } ?: 0
) )
} }
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
containerColor = Constants.AppColors.Surface, containerColor = Color(0xFF1E293B),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
title = { title = {
Text( Text(
"Edit Kategori", "Edit Kategori",
color = Constants.AppColors.OnBackground, color = Color.White,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
}, },
@ -297,51 +227,42 @@ fun EditCategoryDialog(
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { label = { Text("Nama Kategori", color = Color(0xFF94A3B8)) },
Text(
"Nama Kategori",
color = Constants.AppColors.OnSurfaceVariant
)
},
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground, focusedTextColor = Color.White,
unfocusedTextColor = Constants.AppColors.OnSurface, unfocusedTextColor = Color.White,
focusedContainerColor = Constants.AppColors.SurfaceVariant, focusedContainerColor = Color(0xFF334155),
unfocusedContainerColor = Constants.AppColors.SurfaceVariant, unfocusedContainerColor = Color(0xFF334155),
cursorColor = Constants.AppColors.Primary, cursorColor = Color(0xFFA855F7),
focusedBorderColor = Constants.AppColors.Primary, focusedIndicatorColor = Color(0xFFA855F7),
unfocusedBorderColor = Constants.AppColors.Border unfocusedIndicatorColor = Color(0xFF64748B)
), ),
shape = RoundedCornerShape(Constants.Radius.Medium.dp) shape = RoundedCornerShape(12.dp)
) )
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Text( Text(
"Pilih Warna:", "Pilih Gradient:",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurface, color = Color.White,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold
fontSize = 14.sp
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Constants.AppColors.CategoryColors.chunked(4).forEach { row -> gradients.chunked(4).forEach { row ->
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
row.forEachIndexed { _, gradient -> row.forEachIndexed { _, gradient ->
val globalIndex = Constants.AppColors.CategoryColors.indexOf(gradient) val globalIndex = gradients.indexOf(gradient)
val isSelected = selectedGradient == globalIndex
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
.clip(RoundedCornerShape(Constants.Radius.Medium.dp)) .clip(RoundedCornerShape(12.dp))
.background( .background(
brush = Brush.linearGradient( brush = Brush.linearGradient(
colors = listOf( colors = listOf(
@ -353,11 +274,7 @@ fun EditCategoryDialog(
.clickable { selectedGradient = globalIndex }, .clickable { selectedGradient = globalIndex },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
this@Row.AnimatedVisibility( if (selectedGradient == globalIndex) {
visible = isSelected,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
Icon( Icon(
Icons.Default.Check, Icons.Default.Check,
contentDescription = null, contentDescription = null,
@ -376,13 +293,19 @@ fun EditCategoryDialog(
Button( Button(
onClick = { onClick = {
if (name.isNotBlank()) { if (name.isNotBlank()) {
val gradient = Constants.AppColors.CategoryColors[selectedGradient] val gradient = gradients[selectedGradient]
onSave(name, gradient.first, gradient.second) onSave(name, gradient.first, gradient.second)
} }
}, },
enabled = name.isNotBlank(), enabled = name.isNotBlank(),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Primary containerColor = Color.Transparent
),
modifier = Modifier.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
),
shape = RoundedCornerShape(8.dp)
) )
) { ) {
Text("Simpan", color = Color.White, fontWeight = FontWeight.Bold) Text("Simpan", color = Color.White, fontWeight = FontWeight.Bold)
@ -390,7 +313,7 @@ fun EditCategoryDialog(
}, },
dismissButton = { dismissButton = {
TextButton(onClick = onDismiss) { TextButton(onClick = onDismiss) {
Text("Batal", color = Constants.AppColors.OnSurfaceVariant) Text("Batal", color = Color(0xFF94A3B8))
} }
} }
) )

View File

@ -1,28 +1,38 @@
// 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.core.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.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.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.StarBorder import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.* import androidx.compose.material3.Card
import androidx.compose.runtime.* import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
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.data.model.Note import com.example.notesai.data.model.Note
import com.example.notesai.util.Constants
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@ -33,105 +43,86 @@ fun NoteCard(
) { ) {
val dateFormat = SimpleDateFormat("dd MMM, HH:mm", Locale("id", "ID")) val dateFormat = SimpleDateFormat("dd MMM, HH:mm", Locale("id", "ID"))
// Scale animation on press
var isPressed by remember { mutableStateOf(false) }
val scale by animateFloatAsState(
targetValue = if (isPressed) 0.95f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "scale"
)
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.scale(scale) .combinedClickable(
.combinedClickable(onClick = onClick), onClick = onClick,
),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant // Lebih terang dari Surface containerColor = Color(0xFF1E293B)
), ),
elevation = CardDefaults.cardElevation( elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
defaultElevation = 2.dp
)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .padding(16.dp)
) { ) {
// Header: Title + Pin
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
// Title // Judul
Text( Text(
note.title, note.title,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Constants.AppColors.OnBackground, color = Color.White,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis
fontSize = 18.sp
) )
// Pin Button
IconButton( IconButton(
onClick = onPinClick, onClick = onPinClick,
modifier = Modifier.size(32.dp) modifier = Modifier.size(24.dp)
) { ) {
Icon( Icon(
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder, if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
contentDescription = "Pin", contentDescription = "Pin",
tint = if (note.isPinned) Constants.AppColors.Warning else Constants.AppColors.OnSurfaceVariant, tint = if (note.isPinned) Color(0xFFFBBF24) else Color.Gray,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
) )
} }
} }
// Content Preview // Deskripsi
if (note.content.isNotEmpty()) { if (note.content.isNotEmpty()) {
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text(
text = "Deskripsi",
style = MaterialTheme.typography.labelSmall,
color = Color(0xFF94A3B8),
fontWeight = FontWeight.SemiBold
)
Spacer(modifier = Modifier.height(4.dp))
Text( Text(
note.content, note.content,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
maxLines = 4, maxLines = 4,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = Constants.AppColors.OnSurfaceVariant, color = Color(0xFFCBD5E1),
lineHeight = 20.sp, lineHeight = 20.sp
fontSize = 14.sp
) )
} }
Spacer(modifier = Modifier.height(16.dp))
// Divider
HorizontalDivider(
color = Constants.AppColors.Divider,
thickness = 1.dp
)
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
// Footer: Timestamp HorizontalDivider(
Row( color = Color(0xFF334155),
modifier = Modifier.fillMaxWidth(), thickness = 1.dp
horizontalArrangement = Arrangement.SpaceBetween, )
verticalAlignment = Alignment.CenterVertically
) { Spacer(modifier = Modifier.height(8.dp))
Text(
dateFormat.format(Date(note.timestamp)), // Timestamp
style = MaterialTheme.typography.bodySmall, Text(
color = Constants.AppColors.OnSurfaceTertiary, dateFormat.format(Date(note.timestamp)),
fontSize = 12.sp style = MaterialTheme.typography.bodySmall,
) color = Color(0xFF64748B)
} )
} }
} }
} }

View File

@ -1,10 +1,11 @@
// File: util/Constants.kt
package com.example.notesai.util package com.example.notesai.util
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
object Constants { object Constants {
// App Info // App Info
const val APP_NAME = "NotesAI" const val APP_NAME = "AI Notes"
const val APP_VERSION = "1.0.0" const val APP_VERSION = "1.0.0"
// DataStore // DataStore
@ -16,88 +17,37 @@ object Constants {
const val MAX_CHAT_PREVIEW_LINES = 2 const val MAX_CHAT_PREVIEW_LINES = 2
const val GRID_COLUMNS = 2 const val GRID_COLUMNS = 2
// NEW MINIMALIST COLOR PALETTE // Gradients
val GRADIENT_PRESETS = listOf(
Pair(0xFF6366F1L, 0xFFA855F7L),
Pair(0xFFEC4899L, 0xFFF59E0BL),
Pair(0xFF8B5CF6L, 0xFFEC4899L),
Pair(0xFF06B6D4L, 0xFF3B82F6L),
Pair(0xFF10B981L, 0xFF059669L),
Pair(0xFFF59E0BL, 0xFFEF4444L),
Pair(0xFF6366F1L, 0xFF8B5CF6L),
Pair(0xFFEF4444L, 0xFFDC2626L)
)
// Colors
object AppColors { object AppColors {
// Backgrounds - Neutral Dark val Primary = Color(0xFF6366F1)
val Background = Color(0xFF0A0A0A) // Almost black val Secondary = Color(0xFFA855F7)
val Surface = Color(0xFF141414) // Dark gray val Background = Color(0xFF0F172A)
val SurfaceVariant = Color(0xFF1E1E1E) // Lighter dark gray val Surface = Color(0xFF1E293B)
val SurfaceElevated = Color(0xFF252525) // Elevated surface val SurfaceVariant = Color(0xFF334155)
val OnBackground = Color(0xFFE2E8F0)
// Primary Accent - Subtle Blue (minimalist) val OnSurface = Color(0xFFE2E8F0)
val Primary = Color(0xFF3B82F6) // Modern blue val Success = Color(0xFF10B981)
val PrimaryVariant = Color(0xFF60A5FA) // Light blue val Error = Color(0xFFEF4444)
val PrimaryContainer = Color(0xFF1E3A8A) // Dark blue container val Warning = Color(0xFFFBBF24)
val TextSecondary = Color(0xFF94A3B8)
// Secondary Accent - Minimal use val TextTertiary = Color(0xFF64748B)
val Secondary = Color(0xFF8B5CF6) // Subtle purple val Divider = Color(0xFF334155)
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
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
)
} }
// Animation Durations // Animation
const val ANIMATION_DURATION_SHORT = 150 const val ANIMATION_DURATION = 300
const val ANIMATION_DURATION_MEDIUM = 300
const val ANIMATION_DURATION_LONG = 500
const val FADE_IN_DURATION = 200 const val FADE_IN_DURATION = 200
const val FADE_OUT_DURATION = 200 const val FADE_OUT_DURATION = 200
// Spacing System (8dp grid)
object Spacing {
const val ExtraSmall = 4
const val Small = 8
const val Medium = 16
const val Large = 24
const val ExtraLarge = 32
const val XXLarge = 48
}
// Corner Radius
object Radius {
const val Small = 8
const val Medium = 12
const val Large = 16
const val ExtraLarge = 20
const val Round = 999
}
// Elevation
object Elevation {
const val None = 0
const val Small = 2
const val Medium = 4
const val Large = 8
const val ExtraLarge = 16
}
} }

View File

@ -9,9 +9,6 @@ appcompat = "1.6.1"
material = "1.10.0" material = "1.10.0"
activity = "1.8.0" activity = "1.8.0"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
uiText = "1.10.0"
material3 = "1.4.0"
animationCore = "1.10.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -22,9 +19,6 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
material = { group = "com.google.android.material", name = "material", version.ref = "material" } material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
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" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }