Mengubah Warna dan menyesuaikan UI/UX Beranda

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-12-13 23:14:52 +07:00
parent e541c4e234
commit 0f0ac6b8f3
7 changed files with 594 additions and 296 deletions

View File

@ -1,10 +1,11 @@
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
@ -19,6 +20,8 @@ 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
@ -37,20 +40,64 @@ 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 = Color(0xFF6366F1), // Primary colors
secondary = Color(0xFFA855F7), primary = Constants.AppColors.Primary,
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,
onBackground = Color(0xFFE2E8F0), secondaryContainer = Constants.AppColors.SecondaryVariant,
onSurface = Color(0xFFE2E8F0) onSecondaryContainer = Color.White,
// Background colors
background = Constants.AppColors.Background,
onBackground = Constants.AppColors.OnBackground,
// Surface colors
surface = Constants.AppColors.Surface,
onSurface = Constants.AppColors.OnSurface,
surfaceVariant = Constants.AppColors.SurfaceVariant,
onSurfaceVariant = Constants.AppColors.OnSurfaceVariant,
// Error colors
error = Constants.AppColors.Error,
onError = Color.White,
// Other
outline = Constants.AppColors.Border,
outlineVariant = Constants.AppColors.Divider
),
typography = Typography(
// Improve typography for better readability
displayLarge = MaterialTheme.typography.displayLarge.copy(
fontWeight = FontWeight.Bold
),
headlineLarge = MaterialTheme.typography.headlineLarge.copy(
fontWeight = FontWeight.Bold
),
titleLarge = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.SemiBold
),
bodyLarge = MaterialTheme.typography.bodyLarge.copy(
lineHeight = 24.sp
),
bodyMedium = MaterialTheme.typography.bodyMedium.copy(
lineHeight = 20.sp
)
) )
) { ) {
Surface( Surface(
@ -161,7 +208,12 @@ fun NotesApp() {
floatingActionButton = { floatingActionButton = {
AnimatedVisibility( AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote, visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn() + fadeIn(), enter = scaleIn(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
) + fadeIn(),
exit = scaleOut() + fadeOut() exit = scaleOut() + fadeOut()
) { ) {
FloatingActionButton( FloatingActionButton(
@ -173,20 +225,18 @@ fun NotesApp() {
showCategoryDialog = true showCategoryDialog = true
} }
}, },
containerColor = Color.Transparent, containerColor = Constants.AppColors.Primary,
modifier = Modifier contentColor = Color.White,
.shadow(8.dp, CircleShape) elevation = FloatingActionButtonDefaults.elevation(
.background( defaultElevation = 8.dp,
brush = Brush.linearGradient( pressedElevation = 12.dp
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",
tint = Color.White modifier = Modifier.size(28.dp)
) )
} }
} }

View File

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

View File

@ -1,31 +1,32 @@
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.layout.Arrangement import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.shape.CircleShape
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.material3.BottomAppBar import androidx.compose.material.icons.outlined.AutoAwesome
import androidx.compose.material3.Icon import androidx.compose.material.icons.outlined.Home
import androidx.compose.material3.MaterialTheme import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.Text import androidx.compose.material3.*
import androidx.compose.runtime.Composable 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.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(
@ -33,70 +34,125 @@ fun ModernBottomBar(
onHomeClick: () -> Unit, onHomeClick: () -> Unit,
onAIClick: () -> Unit onAIClick: () -> Unit
) { ) {
BottomAppBar( // Floating Bottom Bar with Glassmorphism
containerColor = Color.Transparent, Box(
modifier = Modifier modifier = Modifier
.shadow(8.dp, RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) .fillMaxWidth()
.background( .padding(horizontal = 16.dp)
brush = Brush.verticalGradient(
colors = listOf(
Color(0xFF1E293B).copy(0.95f),
Color(0xFF334155).copy(0.95f)
)
),
shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)
)
) { ) {
Row( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .shadow(
horizontalArrangement = Arrangement.SpaceEvenly, elevation = Constants.Elevation.Large.dp,
verticalAlignment = Alignment.CenterVertically shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
),
color = Constants.AppColors.SurfaceElevated,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
) { ) {
Column( Row(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth()
.clickable(onClick = onHomeClick) .padding(vertical = 8.dp, horizontal = 24.dp),
.padding(vertical = 8.dp) horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( // Home Button
Icons.Default.Home, BottomBarItem(
contentDescription = "Home", selected = currentScreen == "main",
tint = if (currentScreen == "main") Color(0xFF6366F1) else Color(0xFF94A3B8), onClick = onHomeClick,
modifier = Modifier.size(24.dp) icon = if (currentScreen == "main") Icons.Filled.Home else Icons.Outlined.Home,
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
)
}
Column( // AI Button
horizontalAlignment = Alignment.CenterHorizontally, BottomBarItem(
modifier = Modifier selected = currentScreen == "ai",
.weight(1f) onClick = onAIClick,
.clickable(onClick = onAIClick) icon = if (currentScreen == "ai") Icons.Filled.AutoAwesome else Icons.Outlined.AutoAwesome,
.padding(vertical = 8.dp) label = "AI Helper"
) {
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
)
}
}
} }

View File

@ -1,42 +1,25 @@
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.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.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.material.icons.filled.Star import androidx.compose.material3.*
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
@ -50,58 +33,116 @@ fun ModernTopBar(
onSearchQueryChange: (String) -> Unit, onSearchQueryChange: (String) -> Unit,
showSearch: Boolean showSearch: Boolean
) { ) {
TopAppBar( // Smooth transition for search bar
title = { AnimatedContent(
if (showSearch) { targetState = showSearch,
TextField( transitionSpec = {
value = searchQuery, fadeIn(animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM)) togetherWith
onValueChange = onSearchQueryChange, fadeOut(animationSpec = tween(Constants.ANIMATION_DURATION_MEDIUM))
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
)
}
}, },
navigationIcon = { label = "topbar"
IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) { ) { isSearching ->
Icon( if (isSearching) {
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu, // Search Mode - Minimalist
contentDescription = null, Surface(
tint = Color.White modifier = Modifier
) .fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = Constants.AppColors.Surface
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Medium.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Search TextField
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 {
actions = { // Normal Mode - Minimalist
IconButton(onClick = onSearchClick) { Surface(
Icon( modifier = Modifier
if (showSearch) Icons.Default.Close else Icons.Default.Search, .fillMaxWidth()
contentDescription = "Search", .shadow(Constants.Elevation.Small.dp),
tint = Color.White color = Constants.AppColors.Surface
) ) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Small.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Back/Menu Button
IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) {
Icon(
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
contentDescription = if (showBackButton) "Back" else "Menu",
tint = Constants.AppColors.OnSurface
)
}
// Title
Text(
title,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = Constants.AppColors.OnBackground,
modifier = Modifier.weight(1f)
)
// Search Button
IconButton(onClick = onSearchClick) {
Icon(
Icons.Default.Search,
contentDescription = "Search",
tint = Constants.AppColors.OnSurfaceVariant
)
}
}
} }
}, }
colors = TopAppBarDefaults.topAppBarColors( }
containerColor = Color.Transparent
),
modifier = Modifier
.background(
brush = Brush.horizontalGradient(
colors = listOf(Color(0xFF6366F1), Color(0xFFA855F7))
)
)
)
} }

View File

@ -1,4 +1,3 @@
// 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.*
@ -28,7 +27,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 // Parameter baru onCategoryEdit: (Category, String, Long, Long) -> Unit
) { ) {
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
if (selectedCategory == null) { if (selectedCategory == null) {
@ -58,7 +57,12 @@ fun MainScreen(
} else { } else {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2), columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(
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()
@ -102,7 +106,12 @@ fun MainScreen(
} else { } else {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2), columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 100.dp // Extra space untuk floating bottom bar
),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalItemSpacing = 12.dp, verticalItemSpacing = 12.dp,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()

View File

@ -1,11 +1,10 @@
// 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 = "AI Notes" const val APP_NAME = "NotesAI"
const val APP_VERSION = "1.0.0" const val APP_VERSION = "1.0.0"
// DataStore // DataStore
@ -17,37 +16,88 @@ 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
// Gradients // NEW MINIMALIST COLOR PALETTE
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 {
val Primary = Color(0xFF6366F1) // Backgrounds - Neutral Dark
val Secondary = Color(0xFFA855F7) val Background = Color(0xFF0A0A0A) // Almost black
val Background = Color(0xFF0F172A) val Surface = Color(0xFF141414) // Dark gray
val Surface = Color(0xFF1E293B) val SurfaceVariant = Color(0xFF1E1E1E) // Lighter dark gray
val SurfaceVariant = Color(0xFF334155) val SurfaceElevated = Color(0xFF252525) // Elevated surface
val OnBackground = Color(0xFFE2E8F0)
val OnSurface = Color(0xFFE2E8F0) // Primary Accent - Subtle Blue (minimalist)
val Success = Color(0xFF10B981) val Primary = Color(0xFF3B82F6) // Modern blue
val Error = Color(0xFFEF4444) val PrimaryVariant = Color(0xFF60A5FA) // Light blue
val Warning = Color(0xFFFBBF24) val PrimaryContainer = Color(0xFF1E3A8A) // Dark blue container
val TextSecondary = Color(0xFF94A3B8)
val TextTertiary = Color(0xFF64748B) // Secondary Accent - Minimal use
val Divider = Color(0xFF334155) val Secondary = Color(0xFF8B5CF6) // Subtle purple
val SecondaryVariant = Color(0xFFA78BFA) // Light purple
// Text Colors - High contrast for readability
val OnBackground = Color(0xFFFFFFFF) // Pure white
val OnSurface = Color(0xFFE5E5E5) // Off white
val OnSurfaceVariant = Color(0xFF9CA3AF) // Gray text
val OnSurfaceTertiary = Color(0xFF6B7280) // Muted gray
// Functional Colors
val Success = Color(0xFF10B981) // Green
val Error = Color(0xFFEF4444) // Red
val Warning = Color(0xFFFBBF24) // Amber
val Info = Color(0xFF3B82F6) // Blue
// Border & Dividers - Subtle
val Border = Color(0xFF2A2A2A) // Subtle border
val Divider = Color(0xFF1F1F1F) // Very subtle divider
// Overlay & Shadows
val Overlay = Color(0xFF000000).copy(alpha = 0.5f)
val Shadow = Color(0xFF000000).copy(alpha = 0.3f)
// Category Colors - Minimalist Palette (subtle & muted)
val CategoryColors = listOf(
Pair(0xFF3B82F6L, 0xFF60A5FAL), // Blue gradient
Pair(0xFF8B5CF6L, 0xFFA78BFAL), // Purple gradient
Pair(0xFF10B981L, 0xFF34D399L), // Green gradient
Pair(0xFFF59E0BL, 0xFFFBBF24L), // Amber gradient
Pair(0xFFEF4444L, 0xFFF87171L), // Red gradient
Pair(0xFF06B6D4L, 0xFF22D3EEL), // Cyan gradient
Pair(0xFFEC4899L, 0xFFF472B6L), // Pink gradient
Pair(0xFF6366F1L, 0xFF818CF8L) // Indigo gradient
)
} }
// Animation // Animation Durations
const val ANIMATION_DURATION = 300 const val ANIMATION_DURATION_SHORT = 150
const val ANIMATION_DURATION_MEDIUM = 300
const val ANIMATION_DURATION_LONG = 500
const val FADE_IN_DURATION = 200 const val FADE_IN_DURATION = 200
const val FADE_OUT_DURATION = 200 const val FADE_OUT_DURATION = 200
// Spacing System (8dp grid)
object Spacing {
const val ExtraSmall = 4
const val Small = 8
const val Medium = 16
const val Large = 24
const val ExtraLarge = 32
const val XXLarge = 48
}
// Corner Radius
object Radius {
const val Small = 8
const val Medium = 12
const val Large = 16
const val ExtraLarge = 20
const val Round = 999
}
// Elevation
object Elevation {
const val None = 0
const val Small = 2
const val Medium = 4
const val Large = 8
const val ExtraLarge = 16
}
} }

View File

@ -9,6 +9,9 @@ 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" }
@ -19,6 +22,9 @@ 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" }