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

View File

@ -1,11 +1,10 @@
package com.example.notesai
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.layout.*
import androidx.compose.foundation.shape.CircleShape
@ -20,8 +19,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import java.util.UUID
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 com.example.notesai.data.local.DataStoreManager
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 kotlinx.coroutines.delay
// File: MainActivity.kt (Bagian Theme Setup)
// Ganti MaterialTheme di setContent dengan ini:
import com.example.notesai.util.Constants
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
colorScheme = darkColorScheme(
// Primary colors
primary = Constants.AppColors.Primary,
primary = Color(0xFF6366F1),
secondary = Color(0xFFA855F7),
background = Color(0xFF0F172A),
surface = Color(0xFF1E293B),
onPrimary = Color.White,
primaryContainer = Constants.AppColors.PrimaryContainer,
onPrimaryContainer = Color.White,
// Secondary colors
secondary = Constants.AppColors.Secondary,
onSecondary = Color.White,
secondaryContainer = Constants.AppColors.SecondaryVariant,
onSecondaryContainer = Color.White,
// 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
)
onBackground = Color(0xFFE2E8F0),
onSurface = Color(0xFFE2E8F0)
)
) {
Surface(
@ -208,12 +161,7 @@ fun NotesApp() {
floatingActionButton = {
AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
) + fadeIn(),
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
FloatingActionButton(
@ -225,18 +173,20 @@ fun NotesApp() {
showCategoryDialog = true
}
},
containerColor = Constants.AppColors.Primary,
contentColor = Color.White,
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 8.dp,
pressedElevation = 12.dp
containerColor = Color.Transparent,
modifier = Modifier
.shadow(8.dp, CircleShape)
.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
),
modifier = Modifier.size(64.dp)
shape = CircleShape
)
) {
Icon(
Icons.Default.Add,
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
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
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.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.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Delete
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.Constants
@Composable
fun DrawerMenu(
@ -31,233 +45,133 @@ fun DrawerMenu(
onDismiss: () -> Unit,
onItemClick: (String) -> Unit
) {
// Backdrop with blur effect
Box(
modifier = Modifier
.fillMaxSize()
.background(Constants.AppColors.Overlay)
.statusBarsPadding() // Padding untuk status bar
.background(Color.Black.copy(alpha = 0.5f))
.clickable(
onClick = onDismiss,
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
) {
// Drawer Content
Surface(
Card(
modifier = Modifier
.fillMaxHeight()
.width(280.dp)
.width(250.dp)
.align(Alignment.CenterStart)
.clickable(
onClick = {},
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
color = Constants.AppColors.Surface,
shadowElevation = Constants.Elevation.ExtraLarge.dp
shape = RoundedCornerShape(topEnd = 0.dp, bottomEnd = 0.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1E293B)
),
elevation = CardDefaults.cardElevation(defaultElevation = 16.dp)
) {
Column(
modifier = Modifier.fillMaxSize()
) {
// Header - Minimalist
Column(modifier = Modifier.fillMaxSize()) {
// Header Drawer dengan tombol close
Box(
modifier = Modifier
.fillMaxWidth()
.background(
brush = Brush.verticalGradient(
colors = listOf(
Constants.AppColors.Primary.copy(alpha = 0.15f),
Color.Transparent
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
)
)
)
.padding(Constants.Spacing.ExtraLarge.dp)
.padding(24.dp)
) {
Column {
// App Icon with subtle background
Box(
modifier = Modifier
.size(56.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.2f),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Create,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(32.dp)
)
}
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
Text(
Constants.APP_NAME,
style = MaterialTheme.typography.headlineMedium,
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold,
fontSize = 24.sp
)
Spacer(modifier = Modifier.height(Constants.Spacing.ExtraSmall.dp))
Text(
"Smart Note Taking",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurfaceVariant,
fontSize = 14.sp
)
}
}
Spacer(modifier = Modifier.height(Constants.Spacing.Large.dp))
// Menu Items
DrawerMenuItem(
icon = if (currentScreen == "main") Icons.Filled.Home else Icons.Outlined.Home,
text = "Beranda",
isSelected = currentScreen == "main",
onClick = { onItemClick("main") }
)
DrawerMenuItem(
icon = if (currentScreen == "starred") Icons.Filled.Star else Icons.Outlined.StarBorder,
text = "Berbintang",
isSelected = currentScreen == "starred",
onClick = { onItemClick("starred") }
)
DrawerMenuItem(
icon = if (currentScreen == "archive") Icons.Filled.Archive else Icons.Outlined.Archive,
text = "Arsip",
isSelected = currentScreen == "archive",
onClick = { onItemClick("archive") }
)
DrawerMenuItem(
icon = if (currentScreen == "trash") Icons.Filled.Delete else Icons.Outlined.Delete,
text = "Sampah",
isSelected = currentScreen == "trash",
onClick = { onItemClick("trash") }
)
Spacer(modifier = Modifier.weight(1f))
// Footer - Version info
HorizontalDivider(
color = Constants.AppColors.Divider,
modifier = Modifier.padding(horizontal = Constants.Spacing.Medium.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(Constants.Spacing.Medium.dp),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
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)
) {
Column {
Icon(
Icons.Default.AutoAwesome,
Icons.Default.Create,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(12.dp)
tint = Color.White,
modifier = Modifier.size(36.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
"AI",
color = Constants.AppColors.Primary,
fontSize = 11.sp,
"AI Notes",
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
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
"Smart & Modern",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(0.8f)
)
}
// // Tombol Close
// IconButton(
// onClick = onDismiss,
// modifier = Modifier
// .size(40.dp)
// .background(
// Color.White.copy(alpha = 0.2f),
// shape = CircleShape
// )
// ) {
// Icon(
// Icons.Default.Close,
// contentDescription = "Tutup Menu",
// tint = Color.White,
// modifier = Modifier.size(24.dp)
// )
// }
}
}
Spacer(modifier = Modifier.height(16.dp))
// Menu Items
MenuItem(
icon = Icons.Default.Home,
text = "Beranda",
isSelected = currentScreen == "main"
) { onItemClick("main") }
MenuItem(
icon = Icons.Default.Star,
text = "Berbintang",
isSelected = currentScreen == "starred"
) { onItemClick("starred") }
MenuItem(
icon = Icons.Default.Archive,
text = "Arsip",
isSelected = currentScreen == "archive"
) { onItemClick("archive") }
MenuItem(
icon = Icons.Default.Delete,
text = "Sampah",
isSelected = currentScreen == "trash"
) { onItemClick("trash") }
Spacer(modifier = Modifier.weight(1f))
// Footer
Divider(
color = Color.White.copy(alpha = 0.1f),
modifier = Modifier.padding(horizontal = 16.dp)
)
Text(
text = "Version 1.0.0",
style = MaterialTheme.typography.bodySmall,
color = Color.White.copy(alpha = 0.5f),
modifier = Modifier.padding(16.dp)
)
}
}
}
}

View File

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

View File

@ -1,25 +1,42 @@
package com.example.notesai.presentation.components
import androidx.compose.animation.*
import androidx.compose.animation.core.*
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.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
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.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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.util.Constants
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -33,116 +50,58 @@ fun ModernTopBar(
onSearchQueryChange: (String) -> Unit,
showSearch: Boolean
) {
// Smooth transition for search bar
AnimatedContent(
targetState = showSearch,
transitionSpec = {
fadeIn(animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM)) togetherWith
fadeOut(animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM))
},
label = "topbar"
) { isSearching ->
if (isSearching) {
// Search Mode - Minimalist
Surface(
modifier = Modifier
.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
TopAppBar(
title = {
if (showSearch) {
TextField(
value = searchQuery,
onValueChange = onSearchQueryChange,
placeholder = {
Text(
"Cari catatan atau kategori...",
color = Constants.AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
},
placeholder = { Text("Cari catatan...", color = Color.White.copy(0.6f)) },
colors = TextFieldDefaults.colors(
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
cursorColor = Constants.AppColors.Primary,
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color.White,
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)
modifier = Modifier.fillMaxWidth()
)
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
Surface(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = Constants.AppColors.Surface
) {
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)
fontSize = 22.sp
)
// Search Button
}
},
navigationIcon = {
IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) {
Icon(
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
contentDescription = null,
tint = Color.White
)
}
},
actions = {
IconButton(onClick = onSearchClick) {
Icon(
Icons.Default.Search,
if (showSearch) Icons.Default.Close else Icons.Default.Search,
contentDescription = "Search",
tint = Constants.AppColors.OnSurfaceVariant
tint = Color.White
)
}
}
}
}
}
},
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
import androidx.compose.animation.*
import androidx.compose.animation.core.*
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.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Folder
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
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.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.type.generationConfig
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
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
import kotlin.collections.plus
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -59,8 +89,8 @@ fun AIHelperScreen(
// Inisialisasi Gemini Model
val generativeModel = remember {
GenerativeModel(
modelName = "gemini-2.0-flash-exp",
apiKey = com.example.notesai.config.APIKey.GEMINI_API_KEY,
modelName = "gemini-2.5-flash",
apiKey = APIKey.GEMINI_API_KEY,
generationConfig = generationConfig {
temperature = 0.8f
topK = 40
@ -80,58 +110,94 @@ fun AIHelperScreen(
}
Column(
modifier = Modifier
modifier = Modifier.Companion
.fillMaxSize()
.background(Constants.AppColors.Background)
.background(MaterialTheme.colorScheme.background)
) {
// Category Selector & Stats - Compact
Surface(
color = Constants.AppColors.Surface,
shadowElevation = 2.dp
// Header
Card(
modifier = Modifier.Companion.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color.Companion.Transparent),
shape = RoundedCornerShape(0.dp)
) {
Column(
modifier = Modifier
Box(
modifier = Modifier.Companion
.fillMaxWidth()
.background(
brush = Brush.Companion.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
)
)
.padding(20.dp)
) {
Column {
Row(verticalAlignment = Alignment.Companion.CenterVertically) {
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)
)
}
}
}
}
}
// Category Selector & Stats - Compact Version
Column(
modifier = Modifier.Companion
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
) {
// Category Selector
Box {
Card(
onClick = { showCategoryDropdown = !showCategoryDropdown },
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant
),
shape = RoundedCornerShape(12.dp)
modifier = Modifier.Companion
.fillMaxWidth()
.clickable { showCategoryDropdown = !showCategoryDropdown },
colors = CardDefaults.cardColors(containerColor = Color(0xFF1E293B)),
shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier
modifier = Modifier.Companion
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
verticalAlignment = Alignment.Companion.CenterVertically
) {
Row(verticalAlignment = Alignment.Companion.CenterVertically) {
Icon(
Icons.Default.Folder,
contentDescription = null,
tint = Constants.AppColors.Primary,
modifier = Modifier.size(20.dp)
tint = Color(0xFF6366F1),
modifier = Modifier.Companion.size(20.dp)
)
Spacer(modifier = Modifier.Companion.width(8.dp))
Text(
selectedCategory?.name ?: "Semua Kategori",
color = Constants.AppColors.OnSurface,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
color = Color.Companion.White,
style = MaterialTheme.typography.bodyMedium
)
}
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
tint = Constants.AppColors.OnSurfaceVariant
tint = Color(0xFF94A3B8)
)
}
}
@ -139,12 +205,12 @@ fun AIHelperScreen(
DropdownMenu(
expanded = showCategoryDropdown,
onDismissRequest = { showCategoryDropdown = false },
modifier = Modifier
.fillMaxWidth(0.9f)
.background(Constants.AppColors.SurfaceElevated)
modifier = Modifier.Companion
.fillMaxWidth()
.background(Color(0xFF1E293B))
) {
DropdownMenuItem(
text = { Text("Semua Kategori", color = Constants.AppColors.OnSurface) },
text = { Text("Semua Kategori", color = Color.Companion.White) },
onClick = {
selectedCategory = null
showCategoryDropdown = false
@ -152,7 +218,7 @@ fun AIHelperScreen(
)
categories.forEach { category ->
DropdownMenuItem(
text = { Text(category.name, color = Constants.AppColors.OnSurface) },
text = { Text(category.name, color = Color.Companion.White) },
onClick = {
selectedCategory = category
showCategoryDropdown = false
@ -162,9 +228,8 @@ fun AIHelperScreen(
}
}
Spacer(modifier = Modifier.height(12.dp))
// Stats - Compact
Spacer(modifier = Modifier.Companion.height(12.dp))
val filteredNotes = if (selectedCategory != null) {
notes.filter { it.categoryId == selectedCategory!!.id && !it.isArchived }
} else {
@ -172,105 +237,90 @@ fun AIHelperScreen(
}
Row(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.Companion.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
CompactStatItem(
icon = Icons.Default.Description,
label = "Total",
value = filteredNotes.size.toString(),
label = "Catatan"
color = Color(0xFF6366F1)
)
CompactStatItem(
icon = Icons.Default.Star,
label = "Dipasang",
value = filteredNotes.count { it.isPinned }.toString(),
label = "Dipasang"
color = Color(0xFFFBBF24)
)
CompactStatItem(
icon = Icons.Default.Folder,
label = "Kategori",
value = categories.size.toString(),
label = "Kategori"
color = Color(0xFFA855F7)
)
}
}
}
HorizontalDivider(color = Constants.AppColors.Divider)
Divider(color = Color(0xFF334155), thickness = 1.dp)
// Chat Area
Box(
modifier = Modifier
Column(
modifier = Modifier.Companion
.weight(1f)
.fillMaxWidth()
) {
if (chatMessages.isEmpty()) {
// Welcome State
Column(
modifier = Modifier
modifier = Modifier.Companion
.fillMaxSize()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
horizontalAlignment = Alignment.Companion.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier
.size(80.dp)
.background(
color = Constants.AppColors.Primary.copy(alpha = 0.1f),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.AutoAwesome,
Icons.Default.Star,
contentDescription = null,
modifier = Modifier.size(40.dp),
tint = Constants.AppColors.Primary
modifier = Modifier.Companion.size(64.dp),
tint = Color(0xFF6366F1).copy(0.5f)
)
}
Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.Companion.height(16.dp))
Text(
"AI Assistant",
style = MaterialTheme.typography.headlineMedium,
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold
"Mulai Percakapan",
style = MaterialTheme.typography.titleLarge,
color = Color.Companion.White,
fontWeight = FontWeight.Companion.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Spacer(modifier = Modifier.Companion.height(8.dp))
Text(
"Tanyakan apa saja tentang catatan Anda",
style = MaterialTheme.typography.bodyLarge,
color = Constants.AppColors.OnSurfaceVariant,
textAlign = TextAlign.Center
style = MaterialTheme.typography.bodyMedium,
color = Color(0xFF94A3B8),
textAlign = TextAlign.Companion.Center
)
Spacer(modifier = Modifier.height(32.dp))
Spacer(modifier = Modifier.Companion.height(24.dp))
// Suggestion Chips
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth(0.85f)
horizontalAlignment = Alignment.Companion.Start,
modifier = Modifier.Companion.fillMaxWidth(0.8f)
) {
Text(
"Contoh pertanyaan:",
style = MaterialTheme.typography.labelMedium,
color = Constants.AppColors.OnSurfaceTertiary
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF64748B),
modifier = Modifier.Companion.padding(bottom = 8.dp)
)
SuggestionChip("Analisis catatan saya") { prompt = it }
SuggestionChip("Buat ringkasan") { prompt = it }
SuggestionChip("Berikan saran organisasi") { prompt = it }
SuggestionChip("Analisis catatan saya", onSelect = { prompt = it })
SuggestionChip("Buat ringkasan", onSelect = { prompt = it })
SuggestionChip("Berikan saran organisasi", onSelect = { prompt = it })
}
}
} else {
// Chat Messages
Column(
modifier = Modifier
modifier = Modifier.Companion
.fillMaxSize()
.verticalScroll(scrollState)
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 100.dp)
.padding(16.dp)
) {
chatMessages.forEach { message ->
ChatBubble(
@ -286,34 +336,36 @@ fun AIHelperScreen(
},
showCopied = showCopiedMessage && copiedMessageId == message.id
)
Spacer(modifier = Modifier.height(12.dp))
Spacer(modifier = Modifier.Companion.height(12.dp))
}
// Loading Indicator
if (isLoading) {
Row(
modifier = Modifier
modifier = Modifier.Companion
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.Start
) {
Surface(
color = Constants.AppColors.SurfaceVariant,
shape = RoundedCornerShape(16.dp)
Card(
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1E293B)
),
shape = androidx.compose.foundation.shape.RoundedCornerShape(16.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
modifier = Modifier.Companion.padding(16.dp),
verticalAlignment = Alignment.Companion.CenterVertically
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = Constants.AppColors.Primary,
modifier = Modifier.Companion.size(20.dp),
color = Color(0xFF6366F1),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.Companion.width(12.dp))
Text(
"AI sedang berpikir...",
color = Constants.AppColors.OnSurfaceVariant,
color = Color(0xFF94A3B8),
style = MaterialTheme.typography.bodyMedium
)
}
@ -323,46 +375,52 @@ fun AIHelperScreen(
// Error Message
if (errorMessage.isNotEmpty()) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Constants.AppColors.Error.copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
Card(
modifier = Modifier.Companion.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Color(0xFFEF4444).copy(0.2f)
),
shape = androidx.compose.foundation.shape.RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
modifier = Modifier.Companion.padding(12.dp),
verticalAlignment = Alignment.Companion.CenterVertically
) {
Icon(
Icons.Default.Warning,
contentDescription = null,
tint = Constants.AppColors.Error,
modifier = Modifier.size(20.dp)
tint = Color(0xFFEF4444),
modifier = Modifier.Companion.size(20.dp)
)
Spacer(modifier = Modifier.Companion.width(8.dp))
Text(
errorMessage,
color = Constants.AppColors.Error,
color = Color(0xFFEF4444),
style = MaterialTheme.typography.bodySmall
)
}
}
}
Spacer(modifier = Modifier.Companion.height(80.dp))
}
}
}
// Input Area - Minimalist
Surface(
color = Constants.AppColors.Surface,
shadowElevation = 8.dp,
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
// Input Area
Card(
modifier = Modifier.Companion.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1E293B)
),
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) {
Row(
modifier = Modifier
modifier = Modifier.Companion
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(12.dp)
verticalAlignment = Alignment.Companion.Bottom
) {
OutlinedTextField(
value = prompt,
@ -370,30 +428,33 @@ fun AIHelperScreen(
placeholder = {
Text(
"Ketik pesan...",
color = Constants.AppColors.OnSurfaceTertiary
color = Color(0xFF64748B)
)
},
modifier = Modifier
modifier = Modifier.Companion
.weight(1f)
.heightIn(min = 48.dp, max = 120.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
unfocusedBorderColor = Color.Transparent
colors = TextFieldDefaults.colors(
focusedTextColor = Color.Companion.White,
unfocusedTextColor = Color.Companion.White,
focusedContainerColor = Color(0xFF334155),
unfocusedContainerColor = Color(0xFF334155),
cursorColor = Color(0xFFA855F7),
focusedIndicatorColor = Color(0xFF6366F1),
unfocusedIndicatorColor = Color(0xFF475569)
),
shape = RoundedCornerShape(24.dp),
shape = androidx.compose.foundation.shape.RoundedCornerShape(24.dp),
maxLines = 4
)
Spacer(modifier = Modifier.Companion.width(12.dp))
// Send Button
FloatingActionButton(
onClick = {
if (prompt.isNotBlank() && !isLoading) {
scope.launch {
// Add user message
chatMessages = chatMessages + ChatMessage(
message = prompt,
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 response = result.text ?: "Tidak ada respons dari AI"
// Add AI response
chatMessages = chatMessages + ChatMessage(
message = response,
isUser = false
)
} catch (e: Exception) {
errorMessage = "Error: ${e.message}"
} finally {
@ -440,14 +505,21 @@ fun AIHelperScreen(
}
}
},
containerColor = Constants.AppColors.Primary,
modifier = Modifier.size(48.dp)
containerColor = Color.Companion.Transparent,
modifier = Modifier.Companion
.size(48.dp)
.background(
brush = Brush.Companion.linearGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
),
shape = CircleShape
)
) {
Icon(
Icons.Default.Send,
contentDescription = "Send",
tint = Color.White,
modifier = Modifier.size(20.dp)
tint = Color.Companion.White,
modifier = Modifier.Companion.size(24.dp)
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +1,21 @@
// File: presentation/screens/main/components/CategoryCard.kt
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.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.FolderOpen
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.Category
import com.example.notesai.util.Constants
@Composable
fun CategoryCard(
@ -35,33 +28,16 @@ fun CategoryCard(
var showDeleteConfirm by remember { mutableStateOf(false) }
var showEditDialog 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
if (showDeleteConfirm) {
AlertDialog(
onDismissRequest = { showDeleteConfirm = false },
title = {
Text(
"Pindahkan ke Sampah?",
color = Constants.AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
},
title = { Text("Pindahkan ke Sampah?", color = Color.White) },
text = {
Text(
"Kategori '${category.name}' dan $noteCount catatan di dalamnya akan dipindahkan ke sampah.",
color = Constants.AppColors.OnSurfaceVariant
"Kategori '${category.name}' dan semua catatan di dalamnya akan dipindahkan ke sampah.",
color = Color.White
)
},
confirmButton = {
@ -71,19 +47,23 @@ fun CategoryCard(
showDeleteConfirm = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Constants.AppColors.Error
containerColor = Color(0xFFEF4444)
)
) {
Text("Hapus", color = Color.White)
}
},
dismissButton = {
TextButton(onClick = { showDeleteConfirm = false }) {
Text("Batal", color = Constants.AppColors.OnSurfaceVariant)
Button(
onClick = { showDeleteConfirm = false },
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF64748B)
)
) {
Text("Batal", color = Color.White)
}
},
containerColor = Constants.AppColors.Surface,
shape = RoundedCornerShape(Constants.Radius.Large.dp)
containerColor = Color(0xFF1E293B)
)
}
@ -99,19 +79,13 @@ fun CategoryCard(
)
}
// Main Card - Minimalist Design
Card(
modifier = Modifier
.fillMaxWidth()
.scale(scale)
.clickable(onClick = onClick),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.Surface
),
elevation = CardDefaults.cardElevation(
defaultElevation = Constants.Elevation.Small.dp
)
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
) {
Box(
modifier = Modifier
@ -119,81 +93,67 @@ fun CategoryCard(
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(category.gradientStart).copy(alpha = 0.1f),
Color(category.gradientEnd).copy(alpha = 0.05f)
Color(category.gradientStart),
Color(category.gradientEnd)
)
)
)
.padding(20.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(Constants.Spacing.Large.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
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
) {
Column {
Icon(
Icons.Outlined.FolderOpen,
Icons.Default.Folder,
contentDescription = null,
tint = Color(category.gradientStart),
modifier = Modifier.size(24.dp)
tint = Color.White.copy(0.9f),
modifier = Modifier.size(32.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
category.name,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(4.dp))
Text(
"$noteCount catatan",
style = MaterialTheme.typography.bodySmall,
color = Color.White.copy(0.8f)
)
}
// Menu Button
Box {
// Menu Button (Titik Tiga)
Box(
modifier = Modifier.align(Alignment.TopEnd)
) {
IconButton(
onClick = { showMenu = true },
modifier = Modifier.size(32.dp)
onClick = { showMenu = true }
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = Constants.AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
tint = Color.White.copy(0.9f)
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(Constants.AppColors.SurfaceElevated)
modifier = Modifier.background(Color(0xFF1E293B))
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
horizontalArrangement = Arrangement.spacedBy(8.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
tint = Color(0xFF6366F1),
modifier = Modifier.size(20.dp)
)
Text("Edit Kategori", color = Color.White)
}
},
onClick = {
@ -206,19 +166,15 @@ fun CategoryCard(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
horizontalArrangement = Arrangement.spacedBy(8.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
tint = Color(0xFFEF4444),
modifier = Modifier.size(20.dp)
)
Text("Pindah ke Sampah", color = Color.White)
}
},
onClick = {
@ -229,40 +185,6 @@ fun CategoryCard(
}
}
}
Spacer(modifier = Modifier.height(Constants.Spacing.Medium.dp))
// Category Name
Text(
category.name,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Constants.AppColors.OnBackground,
fontSize = 18.sp
)
Spacer(modifier = Modifier.height(Constants.Spacing.ExtraSmall.dp))
// Note Count dengan subtle styling
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
Icons.Default.Description,
contentDescription = null,
tint = Constants.AppColors.OnSurfaceTertiary,
modifier = Modifier.size(14.dp)
)
Text(
"$noteCount catatan",
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
fontSize = 13.sp
)
}
}
}
}
}
@ -272,23 +194,31 @@ fun EditCategoryDialog(
onDismiss: () -> 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 selectedGradient by remember {
mutableStateOf(
Constants.AppColors.CategoryColors.indexOfFirst {
gradients.indexOfFirst {
it.first == category.gradientStart && it.second == category.gradientEnd
}.takeIf { it >= 0 } ?: 0
)
}
AlertDialog(
onDismissRequest = onDismiss,
containerColor = Constants.AppColors.Surface,
shape = RoundedCornerShape(Constants.Radius.Large.dp),
containerColor = Color(0xFF1E293B),
title = {
Text(
"Edit Kategori",
color = Constants.AppColors.OnBackground,
color = Color.White,
fontWeight = FontWeight.Bold
)
},
@ -297,51 +227,42 @@ fun EditCategoryDialog(
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = {
Text(
"Nama Kategori",
color = Constants.AppColors.OnSurfaceVariant
)
},
label = { Text("Nama Kategori", color = Color(0xFF94A3B8)) },
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = Constants.AppColors.OnBackground,
unfocusedTextColor = Constants.AppColors.OnSurface,
focusedContainerColor = Constants.AppColors.SurfaceVariant,
unfocusedContainerColor = Constants.AppColors.SurfaceVariant,
cursorColor = Constants.AppColors.Primary,
focusedBorderColor = Constants.AppColors.Primary,
unfocusedBorderColor = Constants.AppColors.Border
colors = TextFieldDefaults.colors(
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
focusedContainerColor = Color(0xFF334155),
unfocusedContainerColor = Color(0xFF334155),
cursorColor = Color(0xFFA855F7),
focusedIndicatorColor = Color(0xFFA855F7),
unfocusedIndicatorColor = Color(0xFF64748B)
),
shape = RoundedCornerShape(Constants.Radius.Medium.dp)
shape = RoundedCornerShape(12.dp)
)
Spacer(modifier = Modifier.height(20.dp))
Text(
"Pilih Warna:",
"Pilih Gradient:",
style = MaterialTheme.typography.bodyMedium,
color = Constants.AppColors.OnSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp
color = Color.White,
fontWeight = FontWeight.SemiBold
)
Spacer(modifier = Modifier.height(12.dp))
Constants.AppColors.CategoryColors.chunked(4).forEach { row ->
gradients.chunked(4).forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
row.forEachIndexed { _, gradient ->
val globalIndex = Constants.AppColors.CategoryColors.indexOf(gradient)
val isSelected = selectedGradient == globalIndex
val globalIndex = gradients.indexOf(gradient)
Box(
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.clip(RoundedCornerShape(Constants.Radius.Medium.dp))
.clip(RoundedCornerShape(12.dp))
.background(
brush = Brush.linearGradient(
colors = listOf(
@ -353,11 +274,7 @@ fun EditCategoryDialog(
.clickable { selectedGradient = globalIndex },
contentAlignment = Alignment.Center
) {
this@Row.AnimatedVisibility(
visible = isSelected,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
if (selectedGradient == globalIndex) {
Icon(
Icons.Default.Check,
contentDescription = null,
@ -376,13 +293,19 @@ fun EditCategoryDialog(
Button(
onClick = {
if (name.isNotBlank()) {
val gradient = Constants.AppColors.CategoryColors[selectedGradient]
val gradient = gradients[selectedGradient]
onSave(name, gradient.first, gradient.second)
}
},
enabled = name.isNotBlank(),
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)
@ -390,7 +313,7 @@ fun EditCategoryDialog(
},
dismissButton = {
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
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
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.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material3.Card
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.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.data.model.Note
import com.example.notesai.util.Constants
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Locale
@OptIn(ExperimentalFoundationApi::class)
@Composable
@ -33,105 +43,86 @@ fun NoteCard(
) {
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(
modifier = Modifier
.fillMaxWidth()
.scale(scale)
.combinedClickable(onClick = onClick),
.combinedClickable(
onClick = onClick,
),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = Constants.AppColors.SurfaceVariant // Lebih terang dari Surface
containerColor = Color(0xFF1E293B)
),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
)
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
// Header: Title + Pin
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
// Title
// Judul
Text(
note.title,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Constants.AppColors.OnBackground,
color = Color.White,
modifier = Modifier.weight(1f),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
fontSize = 18.sp
overflow = TextOverflow.Ellipsis
)
// Pin Button
IconButton(
onClick = onPinClick,
modifier = Modifier.size(32.dp)
modifier = Modifier.size(24.dp)
) {
Icon(
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
contentDescription = "Pin",
tint = if (note.isPinned) Constants.AppColors.Warning else Constants.AppColors.OnSurfaceVariant,
tint = if (note.isPinned) Color(0xFFFBBF24) else Color.Gray,
modifier = Modifier.size(18.dp)
)
}
}
// Content Preview
// Deskripsi
if (note.content.isNotEmpty()) {
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(
note.content,
style = MaterialTheme.typography.bodyMedium,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
color = Constants.AppColors.OnSurfaceVariant,
lineHeight = 20.sp,
fontSize = 14.sp
color = Color(0xFFCBD5E1),
lineHeight = 20.sp
)
}
Spacer(modifier = Modifier.height(16.dp))
// Divider
HorizontalDivider(
color = Constants.AppColors.Divider,
thickness = 1.dp
)
Spacer(modifier = Modifier.height(12.dp))
// Footer: Timestamp
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
HorizontalDivider(
color = Color(0xFF334155),
thickness = 1.dp
)
Spacer(modifier = Modifier.height(8.dp))
// Timestamp
Text(
dateFormat.format(Date(note.timestamp)),
style = MaterialTheme.typography.bodySmall,
color = Constants.AppColors.OnSurfaceTertiary,
fontSize = 12.sp
color = Color(0xFF64748B)
)
}
}
}
}

View File

@ -1,10 +1,11 @@
// File: util/Constants.kt
package com.example.notesai.util
import androidx.compose.ui.graphics.Color
object Constants {
// App Info
const val APP_NAME = "NotesAI"
const val APP_NAME = "AI Notes"
const val APP_VERSION = "1.0.0"
// DataStore
@ -16,88 +17,37 @@ object Constants {
const val MAX_CHAT_PREVIEW_LINES = 2
const val GRID_COLUMNS = 2
// NEW MINIMALIST COLOR PALETTE
object AppColors {
// Backgrounds - Neutral Dark
val Background = Color(0xFF0A0A0A) // Almost black
val Surface = Color(0xFF141414) // Dark gray
val SurfaceVariant = Color(0xFF1E1E1E) // Lighter dark gray
val SurfaceElevated = Color(0xFF252525) // Elevated surface
// Primary Accent - Subtle Blue (minimalist)
val Primary = Color(0xFF3B82F6) // Modern blue
val PrimaryVariant = Color(0xFF60A5FA) // Light blue
val PrimaryContainer = Color(0xFF1E3A8A) // Dark blue container
// Secondary Accent - Minimal use
val Secondary = Color(0xFF8B5CF6) // Subtle purple
val SecondaryVariant = Color(0xFFA78BFA) // Light purple
// Text Colors - High contrast for readability
val OnBackground = Color(0xFFFFFFFF) // Pure white
val OnSurface = Color(0xFFE5E5E5) // Off white
val OnSurfaceVariant = Color(0xFF9CA3AF) // Gray text
val OnSurfaceTertiary = Color(0xFF6B7280) // Muted gray
// Functional Colors
val Success = Color(0xFF10B981) // Green
val Error = Color(0xFFEF4444) // Red
val Warning = Color(0xFFFBBF24) // Amber
val Info = Color(0xFF3B82F6) // Blue
// Border & Dividers - Subtle
val Border = Color(0xFF2A2A2A) // Subtle border
val Divider = Color(0xFF1F1F1F) // Very subtle divider
// Overlay & Shadows
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
// 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 {
val Primary = Color(0xFF6366F1)
val Secondary = Color(0xFFA855F7)
val Background = Color(0xFF0F172A)
val Surface = Color(0xFF1E293B)
val SurfaceVariant = Color(0xFF334155)
val OnBackground = Color(0xFFE2E8F0)
val OnSurface = Color(0xFFE2E8F0)
val Success = Color(0xFF10B981)
val Error = Color(0xFFEF4444)
val Warning = Color(0xFFFBBF24)
val TextSecondary = Color(0xFF94A3B8)
val TextTertiary = Color(0xFF64748B)
val Divider = Color(0xFF334155)
}
// Animation Durations
const val ANIMATION_DURATION_SHORT = 150
const val ANIMATION_DURATION_MEDIUM = 300
const val ANIMATION_DURATION_LONG = 500
// Animation
const val ANIMATION_DURATION = 300
const val FADE_IN_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"
activity = "1.8.0"
constraintlayout = "2.1.4"
uiText = "1.10.0"
material3 = "1.4.0"
animationCore = "1.10.0"
[libraries]
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" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
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]
android-application = { id = "com.android.application", version.ref = "agp" }