Compare commits

..

2 Commits

6 changed files with 327 additions and 150 deletions

View File

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

View File

@ -4,6 +4,7 @@ import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
@ -14,6 +15,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
@ -34,6 +36,22 @@ fun ModernTopBar(
onSearchQueryChange: (String) -> Unit,
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
AnimatedContent(
targetState = showSearch,
@ -44,84 +62,86 @@ fun ModernTopBar(
label = "topbar"
) { isSearching ->
if (isSearching) {
// Search Mode - Minimalist
Surface(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = AppColors.Surface
) {
// Search Mode
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Medium.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Search TextField
TextField(
OutlinedTextField(
value = searchQuery,
onValueChange = onSearchQueryChange,
placeholder = {
Text(
"Cari catatan atau kategori...",
"Cari...",
color = AppColors.OnSurfaceVariant,
fontSize = 15.sp
)
},
colors = TextFieldDefaults.colors(
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = AppColors.SurfaceVariant,
unfocusedContainerColor = AppColors.SurfaceVariant,
focusedTextColor = AppColors.OnBackground,
unfocusedTextColor = AppColors.OnSurface,
cursorColor = AppColors.Primary,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
focusedBorderColor = AppColors.Primary,
unfocusedBorderColor = Color.Transparent
),
modifier = Modifier
.weight(1f)
.heightIn(min = 48.dp),
shape = RoundedCornerShape(Constants.Radius.Medium.dp),
shape = RoundedCornerShape(Constants.Radius.Large.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()
}
},
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(AppColors.SurfaceVariant)
) {
Icon(
Icons.Default.Close,
contentDescription = "Close Search",
tint = AppColors.OnSurfaceVariant
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
}
}
} else {
// Normal Mode - Minimalist
Surface(
modifier = Modifier
.fillMaxWidth()
.shadow(Constants.Elevation.Small.dp),
color = AppColors.Surface
) {
// Normal Mode
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = Constants.Spacing.Small.dp, vertical = Constants.Spacing.Small.dp),
verticalAlignment = Alignment.CenterVertically
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
// Back/Menu Button
IconButton(onClick = if (showBackButton) onBackClick else onMenuClick) {
IconButton(
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(
if (showBackButton) Icons.AutoMirrored.Filled.ArrowBack else Icons.Default.Menu,
contentDescription = if (showBackButton) "Back" else "Menu",
tint = AppColors.OnSurface
tint = if (showBackButton) AppColors.OnSurface else AppColors.Primary,
modifier = Modifier.size(20.dp)
)
}
@ -129,21 +149,31 @@ fun ModernTopBar(
Text(
title,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
fontSize = 18.sp,
color = AppColors.OnBackground,
modifier = Modifier.weight(1f)
modifier = Modifier
.weight(1f)
.padding(horizontal = 12.dp)
)
// Search Button
IconButton(onClick = onSearchClick) {
IconButton(
onClick = onSearchClick,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(AppColors.SurfaceVariant)
) {
Icon(
Icons.Default.Search,
contentDescription = "Search",
tint = AppColors.OnSurfaceVariant
tint = AppColors.OnSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
}
}
}
}
}
}

View File

@ -14,6 +14,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
@ -22,6 +23,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.notesai.config.APIKey
import com.example.notesai.data.local.DataStoreManager
import com.example.notesai.data.model.*
import com.example.notesai.util.Constants
@ -35,7 +37,6 @@ 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.SuggestionChip
import com.example.notesai.util.AppColors
import com.example.notesai.config.APIKey
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

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

View File

@ -3,11 +3,12 @@ package com.example.notesai.presentation.screens.main.components
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.*
import androidx.compose.runtime.*
@ -30,9 +31,13 @@ import java.util.*
fun NoteCard(
note: Note,
onClick: () -> Unit,
onPinClick: () -> Unit
onPinClick: () -> Unit,
onEdit: () -> Unit = {},
onDelete: () -> Unit = {}
) {
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
var isPressed by remember { mutableStateOf(false) }
@ -45,31 +50,78 @@ fun NoteCard(
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(
modifier = Modifier
.fillMaxWidth()
.scale(scale)
.combinedClickable(onClick = onClick),
shape = RoundedCornerShape(16.dp),
shape = RoundedCornerShape(Constants.Radius.Large.dp),
colors = CardDefaults.cardColors(
containerColor = AppColors.SurfaceVariant
),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
defaultElevation = Constants.Elevation.Small.dp
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.padding(Constants.Spacing.Large.dp)
) {
// Header: Title + Pin
// Header: Title + Actions (Vertical)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
// Title
// Title - takes most space
Text(
note.title,
style = MaterialTheme.typography.titleLarge,
@ -81,10 +133,86 @@ fun NoteCard(
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
IconButton(
onClick = onPinClick,
modifier = Modifier.size(32.dp)
modifier = Modifier.size(28.dp)
) {
Icon(
if (note.isPinned) Icons.Filled.Star else Icons.Outlined.StarBorder,
@ -94,6 +222,7 @@ fun NoteCard(
)
}
}
}
// Deskripsi Preview
if (note.description.isNotEmpty()) {

View File

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