Compare commits
No commits in common. "80774b58eaa6e9001c8bd04070cce0877a6e58f9" and "e541c4e234e3f3b6d25e1023f4be72a38e1493d9" have entirely different histories.
80774b58ea
...
e541c4e234
@ -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")
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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))
|
||||||
|
Text(
|
||||||
|
"Home",
|
||||||
|
color = if (currentScreen == "main") Color(0xFF6366F1) else Color(0xFF94A3B8),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = if (currentScreen == "main") FontWeight.Bold else FontWeight.Normal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// AI Button
|
Column(
|
||||||
BottomBarItem(
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
selected = currentScreen == "ai",
|
modifier = Modifier
|
||||||
onClick = onAIClick,
|
.weight(1f)
|
||||||
icon = if (currentScreen == "ai") Icons.Filled.AutoAwesome else Icons.Outlined.AutoAwesome,
|
.clickable(onClick = onAIClick)
|
||||||
label = "AI Helper"
|
.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Star,
|
||||||
|
contentDescription = "AI Helper",
|
||||||
|
tint = if (currentScreen == "ai") Color(0xFFFBBF24) else Color(0xFF94A3B8),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
"AI Helper",
|
||||||
|
color = if (currentScreen == "ai") Color(0xFFFBBF24) else Color(0xFF94A3B8),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = if (currentScreen == "ai") FontWeight.Bold else FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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(
|
|
||||||
icon,
|
|
||||||
contentDescription = label,
|
|
||||||
tint = iconColor,
|
|
||||||
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,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 12.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -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" }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user