Compare commits

..

No commits in common. "05b7a2a71b0fed99a773c202e4db854d75f89c61" and "b264f87b14af0de38dd35e0481d06a95bfb9ba92" have entirely different histories.

6 changed files with 151 additions and 328 deletions

View File

@ -199,9 +199,13 @@ fun NotesApp() {
"trash" -> "Sampah" "trash" -> "Sampah"
else -> "AI Notes" else -> "AI Notes"
}, },
showBackButton = (selectedCategory != null && currentScreen == "main"), showBackButton = (selectedCategory != null && currentScreen == "main") || currentScreen == "starred",
onBackClick = { onBackClick = {
if (currentScreen == "starred") {
currentScreen = "main"
} else {
selectedCategory = null selectedCategory = null
}
}, },
onMenuClick = { drawerState = !drawerState }, onMenuClick = { drawerState = !drawerState },
onSearchClick = { showSearch = !showSearch }, onSearchClick = { showSearch = !showSearch },
@ -350,16 +354,6 @@ fun NotesApp() {
it it
} }
} }
},
onNoteEdit = { note ->
editingNote = note
showNoteDialog = true
},
onNoteDelete = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isDeleted = true)
else it
}
} }
) )
@ -398,6 +392,8 @@ fun NotesApp() {
fullScreenNote = note fullScreenNote = note
showFullScreenNote = true showFullScreenNote = true
}, },
onMenuClick = { drawerState = true },
onBack = { currentScreen = "main" },
onUnpin = { note -> onUnpin = { note ->
notes = notes.map { notes = notes.map {
if (it.id == note.id) it.copy(isPinned = false) if (it.id == note.id) it.copy(isPinned = false)

View File

@ -4,7 +4,6 @@ import androidx.compose.animation.*
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
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.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
@ -15,7 +14,6 @@ import androidx.compose.material3.*
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.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
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
@ -36,22 +34,6 @@ fun ModernTopBar(
onSearchQueryChange: (String) -> Unit, onSearchQueryChange: (String) -> Unit,
showSearch: Boolean showSearch: Boolean
) { ) {
// Floating Top Bar with same style as Bottom Bar
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.shadow(
elevation = Constants.Elevation.Large.dp,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
),
color = AppColors.SurfaceElevated,
shape = RoundedCornerShape(Constants.Radius.ExtraLarge.dp)
) {
// Smooth transition for search bar // Smooth transition for search bar
AnimatedContent( AnimatedContent(
targetState = showSearch, targetState = showSearch,
@ -62,86 +44,84 @@ fun ModernTopBar(
label = "topbar" label = "topbar"
) { isSearching -> ) { isSearching ->
if (isSearching) { if (isSearching) {
// Search Mode // Search Mode - Minimalist
Surface(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = AppColors.Surface
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp), .padding(horizontal = Constants.Spacing.Medium.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
// Search TextField // Search TextField
OutlinedTextField( TextField(
value = searchQuery, value = searchQuery,
onValueChange = onSearchQueryChange, onValueChange = onSearchQueryChange,
placeholder = { placeholder = {
Text( Text(
"Cari...", "Cari catatan atau kategori...",
color = AppColors.OnSurfaceVariant, color = AppColors.OnSurfaceVariant,
fontSize = 15.sp fontSize = 15.sp
) )
}, },
colors = OutlinedTextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = AppColors.SurfaceVariant, focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant, unfocusedContainerColor = AppColors.SurfaceVariant,
focusedTextColor = AppColors.OnBackground, focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface, unfocusedTextColor = AppColors.OnSurface,
cursorColor = AppColors.Primary, cursorColor = AppColors.Primary,
focusedBorderColor = AppColors.Primary, focusedIndicatorColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent unfocusedIndicatorColor = Color.Transparent
), ),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.heightIn(min = 48.dp), .heightIn(min = 48.dp),
shape = RoundedCornerShape(Constants.Radius.Large.dp), shape = RoundedCornerShape(Constants.Radius.Medium.dp),
singleLine = true, singleLine = true,
textStyle = LocalTextStyle.current.copy(fontSize = 15.sp) textStyle = LocalTextStyle.current.copy(fontSize = 15.sp)
) )
Spacer(modifier = Modifier.width(Constants.Spacing.Small.dp))
// Close Search Button // Close Search Button
IconButton( IconButton(
onClick = { onClick = {
onSearchQueryChange("") onSearchQueryChange("")
onSearchClick() onSearchClick()
}, }
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(AppColors.SurfaceVariant)
) { ) {
Icon( Icon(
Icons.Default.Close, Icons.Default.Close,
contentDescription = "Close Search", contentDescription = "Close Search",
tint = AppColors.OnSurfaceVariant, tint = AppColors.OnSurfaceVariant
modifier = Modifier.size(20.dp)
) )
} }
} }
}
} else { } else {
// Normal Mode // Normal Mode - Minimalist
Surface(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = AppColors.Surface
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp), .padding(horizontal = Constants.Spacing.Small.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
// Back/Menu Button // Back/Menu Button
IconButton( IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) {
onClick = if (showBackButton) onBackClick else onMenuClick,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(
if (showBackButton) AppColors.SurfaceVariant
else AppColors.Primary.copy(alpha = 0.1f)
)
) {
Icon( Icon(
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu, if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
contentDescription = if (showBackButton) "Back" else "Menu", contentDescription = if (showBackButton) "Back" else "Menu",
tint = if (showBackButton) AppColors.OnSurface else AppColors.Primary, tint = AppColors.OnSurface
modifier = Modifier.size(20.dp)
) )
} }
@ -149,31 +129,21 @@ fun ModernTopBar(
Text( Text(
title, title,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 18.sp, fontSize = 20.sp,
color = AppColors.OnBackground, color = AppColors.OnBackground,
modifier = Modifier modifier = Modifier.weight(1f)
.weight(1f)
.padding(horizontal = 12.dp)
) )
// Search Button // Search Button
IconButton( IconButton(onClick = onSearchClick) {
onClick = onSearchClick,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(AppColors.SurfaceVariant)
) {
Icon( Icon(
Icons.Default.Search, Icons.Default.Search,
contentDescription = "Search", contentDescription = "Search",
tint = AppColors.OnSurfaceVariant, tint = AppColors.OnSurfaceVariant
modifier = Modifier.size(20.dp)
) )
} }
} }
} }
} }
} }
}
} }

View File

@ -14,7 +14,6 @@ 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.shadow
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.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -23,7 +22,6 @@ 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 androidx.compose.ui.unit.sp
import com.example.notesai.config.APIKey
import com.example.notesai.data.local.DataStoreManager import com.example.notesai.data.local.DataStoreManager
import com.example.notesai.data.model.* import com.example.notesai.data.model.*
import com.example.notesai.util.Constants import com.example.notesai.util.Constants
@ -37,6 +35,7 @@ import com.example.notesai.presentation.screens.ai.components.ChatHistoryDrawer
import com.example.notesai.presentation.screens.ai.components.CompactStatItem import com.example.notesai.presentation.screens.ai.components.CompactStatItem
import com.example.notesai.presentation.screens.ai.components.SuggestionChip import com.example.notesai.presentation.screens.ai.components.SuggestionChip
import com.example.notesai.util.AppColors import com.example.notesai.util.AppColors
import com.example.notesai.config.APIKey
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable

View File

@ -27,9 +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, onCategoryEdit: (Category, String, Long, Long) -> Unit
onNoteEdit: (Note) -> Unit = {},
onNoteDelete: (Note) -> Unit = {}
) { ) {
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
if (selectedCategory == null) { if (selectedCategory == null) {
@ -122,9 +120,7 @@ fun MainScreen(
NoteCard( NoteCard(
note = note, note = note,
onClick = { onNoteClick(note) }, onClick = { onNoteClick(note) },
onPinClick = { onPinToggle(note) }, onPinClick = { onPinToggle(note) }
onEdit = { onNoteEdit(note) },
onDelete = { onNoteDelete(note) }
) )
} }
} }

View File

@ -3,12 +3,11 @@ package com.example.notesai.presentation.screens.main.components
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
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.Star
import androidx.compose.material.icons.outlined.StarBorder import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -31,13 +30,9 @@ import java.util.*
fun NoteCard( fun NoteCard(
note: Note, note: Note,
onClick: () -> Unit, onClick: () -> Unit,
onPinClick: () -> Unit, onPinClick: () -> Unit
onEdit: () -> Unit = {},
onDelete: () -> Unit = {}
) { ) {
val dateFormat = SimpleDateFormat("dd MMM, HH:mm", Locale("id", "ID")) val dateFormat = SimpleDateFormat("dd MMM, HH:mm", Locale("id", "ID"))
var showMenu by remember { mutableStateOf(false) }
var showDeleteConfirm by remember { mutableStateOf(false) }
// Scale animation on press // Scale animation on press
var isPressed by remember { mutableStateOf(false) } var isPressed by remember { mutableStateOf(false) }
@ -50,78 +45,31 @@ fun NoteCard(
label = "scale" label = "scale"
) )
// Delete Confirmation Dialog
if (showDeleteConfirm) {
AlertDialog(
onDismissRequest = { showDeleteConfirm = false },
icon = {
Icon(
Icons.Default.DeleteForever,
contentDescription = null,
tint = AppColors.Error
)
},
title = {
Text(
"Pindahkan ke Sampah?",
color = AppColors.OnBackground,
fontWeight = FontWeight.Bold
)
},
text = {
Text(
"Catatan '${note.title}' akan dipindahkan ke sampah.",
color = AppColors.OnSurfaceVariant
)
},
confirmButton = {
Button(
onClick = {
onDelete()
showDeleteConfirm = false
},
colors = ButtonDefaults.buttonColors(
containerColor = AppColors.Error
)
) {
Text("Hapus", color = Color.White)
}
},
dismissButton = {
TextButton(onClick = { showDeleteConfirm = false }) {
Text("Batal", color = AppColors.OnSurfaceVariant)
}
},
containerColor = AppColors.Surface,
shape = RoundedCornerShape(Constants.Radius.Large.dp)
)
}
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.scale(scale) .scale(scale)
.combinedClickable(onClick = onClick), .combinedClickable(onClick = onClick),
shape = RoundedCornerShape(Constants.Radius.Large.dp), shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = AppColors.SurfaceVariant containerColor = AppColors.SurfaceVariant
), ),
elevation = CardDefaults.cardElevation( elevation = CardDefaults.cardElevation(
defaultElevation = Constants.Elevation.Small.dp defaultElevation = 2.dp
) )
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(Constants.Spacing.Large.dp) .padding(16.dp)
) { ) {
// Header: Title + Actions (Vertical) // Header: Title + Pin
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
// Title - takes most space // Title
Text( Text(
note.title, note.title,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
@ -133,86 +81,10 @@ fun NoteCard(
fontSize = 18.sp fontSize = 18.sp
) )
// Vertical Actions Stack
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(0.dp)
) {
// Menu Button
Box {
IconButton(
onClick = { showMenu = true },
modifier = Modifier.size(28.dp)
) {
Icon(
Icons.Default.MoreVert,
contentDescription = "Menu",
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(18.dp)
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
modifier = Modifier.background(AppColors.SurfaceElevated)
) {
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
tint = AppColors.Primary,
modifier = Modifier.size(18.dp)
)
Text(
"Edit Catatan",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
onEdit()
}
)
DropdownMenuItem(
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Constants.Spacing.Small.dp)
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
tint = AppColors.Error,
modifier = Modifier.size(18.dp)
)
Text(
"Pindah ke Sampah",
color = AppColors.OnSurface,
fontSize = 14.sp
)
}
},
onClick = {
showMenu = false
showDeleteConfirm = true
}
)
}
}
// Pin Button // Pin Button
IconButton( IconButton(
onClick = onPinClick, onClick = onPinClick,
modifier = Modifier.size(28.dp) modifier = Modifier.size(32.dp)
) { ) {
Icon( Icon(
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder, if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
@ -222,7 +94,6 @@ fun NoteCard(
) )
} }
} }
}
// Deskripsi Preview // Deskripsi Preview
if (note.description.isNotEmpty()) { if (note.description.isNotEmpty()) {

View File

@ -1,16 +1,13 @@
package com.example.notesai.presentation.screens.starred package com.example.notesai.presentation.screens.starred
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
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.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.notesai.presentation.components.EmptyState import com.example.notesai.presentation.components.EmptyState
import com.example.notesai.presentation.screens.starred.components.StarredNoteCard import com.example.notesai.presentation.screens.starred.components.StarredNoteCard
@ -23,12 +20,12 @@ fun StarredNotesScreen(
notes: List<Note>, notes: List<Note>,
categories: List<Category>, categories: List<Category>,
onNoteClick: (Note) -> Unit, onNoteClick: (Note) -> Unit,
onMenuClick: () -> Unit,
onBack: () -> Unit,
onUnpin: (Note) -> Unit onUnpin: (Note) -> Unit
) { ) {
val starredNotes = notes.filter { it.isPinned && !it.isArchived && !it.isDeleted } val starredNotes = notes.filter { it.isPinned && !it.isArchived && !it.isDeleted }
.sortedByDescending { it.timestamp }
Column(modifier = Modifier.fillMaxSize()) {
if (starredNotes.isEmpty()) { if (starredNotes.isEmpty()) {
EmptyState( EmptyState(
icon = Icons.Default.Star, icon = Icons.Default.Star,
@ -37,12 +34,7 @@ fun StarredNotesScreen(
) )
} else { } else {
LazyColumn( LazyColumn(
contentPadding = PaddingValues( contentPadding = PaddingValues(16.dp),
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 100.dp // Extra space untuk bottom bar
),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items(starredNotes) { note -> items(starredNotes) { note ->
@ -56,5 +48,4 @@ fun StarredNotesScreen(
} }
} }
} }
}
} }