Memperbaiki Bug tidak tersimpan

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-12-18 15:09:39 +07:00
parent 79f7e33a5a
commit b264f87b14
2 changed files with 85 additions and 79 deletions

View File

@ -149,7 +149,8 @@
* Dark/Light theme toggle (ok) * Dark/Light theme toggle (ok)
* AI Agent Catatan * AI Agent Catatan
* Fungsi AI (Upload File) * Fungsi AI (Upload File)
* Markdown Parser * Markdown Parser (ok)
* Sematkan Category
--- ---

View File

@ -6,21 +6,16 @@ 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.animation.core.spring import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
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.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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
@ -37,55 +32,36 @@ import com.example.notesai.presentation.screens.starred.StarredNotesScreen
import com.example.notesai.presentation.screens.trash.TrashScreen import com.example.notesai.presentation.screens.trash.TrashScreen
import com.example.notesai.data.model.Note import com.example.notesai.data.model.Note
import com.example.notesai.data.model.Category import com.example.notesai.data.model.Category
import com.example.notesai.util.updateWhere
import kotlinx.coroutines.delay
import com.example.notesai.util.AppColors import com.example.notesai.util.AppColors
// File: MainActivity.kt (Bagian Theme Setup)
// Ganti MaterialTheme di setContent dengan ini:
import com.example.notesai.util.Constants import com.example.notesai.util.Constants
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
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 = AppColors.Primary, primary = AppColors.Primary,
onPrimary = Color.White, onPrimary = Color.White,
primaryContainer = AppColors.PrimaryContainer, primaryContainer = AppColors.PrimaryContainer,
onPrimaryContainer = Color.White, onPrimaryContainer = Color.White,
// Secondary colors
secondary = AppColors.Secondary, secondary = AppColors.Secondary,
onSecondary = Color.White, onSecondary = Color.White,
secondaryContainer = AppColors.SecondaryVariant, secondaryContainer = AppColors.SecondaryVariant,
onSecondaryContainer = Color.White, onSecondaryContainer = Color.White,
// Background colors
background = AppColors.Background, background = AppColors.Background,
onBackground = AppColors.OnBackground, onBackground = AppColors.OnBackground,
// Surface colors
surface = AppColors.Surface, surface = AppColors.Surface,
onSurface = AppColors.OnSurface, onSurface = AppColors.OnSurface,
surfaceVariant = AppColors.SurfaceVariant, surfaceVariant = AppColors.SurfaceVariant,
onSurfaceVariant = AppColors.OnSurfaceVariant, onSurfaceVariant = AppColors.OnSurfaceVariant,
// Error colors
error = AppColors.Error, error = AppColors.Error,
onError = Color.White, onError = Color.White,
// Other
outline = AppColors.Border, outline = AppColors.Border,
outlineVariant = AppColors.Divider outlineVariant = AppColors.Divider
), ),
typography = Typography( typography = Typography(
// Improve typography for better readability
displayLarge = MaterialTheme.typography.displayLarge.copy( displayLarge = MaterialTheme.typography.displayLarge.copy(
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
), ),
@ -120,6 +96,7 @@ fun NotesApp() {
val context = LocalContext.current val context = LocalContext.current
val dataStoreManager = remember { DataStoreManager(context) } val dataStoreManager = remember { DataStoreManager(context) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
var categories by remember { mutableStateOf(listOf<Category>()) } var categories by remember { mutableStateOf(listOf<Category>()) }
var notes by remember { mutableStateOf(listOf<Note>()) } var notes by remember { mutableStateOf(listOf<Note>()) }
@ -133,66 +110,85 @@ fun NotesApp() {
var showSearch by remember { mutableStateOf(false) } var showSearch by remember { mutableStateOf(false) }
var showFullScreenNote by remember { mutableStateOf(false) } var showFullScreenNote by remember { mutableStateOf(false) }
var fullScreenNote by remember { mutableStateOf<Note?>(null) } var fullScreenNote by remember { mutableStateOf<Note?>(null) }
// Theme state
var isDarkTheme by remember { mutableStateOf(true) } var isDarkTheme by remember { mutableStateOf(true) }
// Load theme preference - SET THEME HERE // Guard flags to prevent race conditions
var isDataLoaded by remember { mutableStateOf(false) }
// Load theme preference
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
dataStoreManager.themeFlow.collect { theme -> dataStoreManager.themeFlow.collect { theme ->
isDarkTheme = theme == "dark" isDarkTheme = theme == "dark"
AppColors.setTheme(isDarkTheme) // SET GLOBAL THEME AppColors.setTheme(isDarkTheme)
} }
} }
// Load data dari DataStore // Load categories ONCE
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
try { dataStoreManager.categoriesFlow.collect { loadedCategories ->
dataStoreManager.categoriesFlow.collect { loadedCategories -> if (!isDataLoaded) {
android.util.Log.d("NotesApp", "Loading ${loadedCategories.size} categories")
categories = loadedCategories categories = loadedCategories
} }
} catch (e: Exception) {
e.printStackTrace()
} }
} }
// Load notes ONCE
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
try { dataStoreManager.notesFlow.collect { loadedNotes ->
dataStoreManager.notesFlow.collect { loadedNotes -> if (!isDataLoaded) {
android.util.Log.d("NotesApp", "Loading ${loadedNotes.size} notes")
notes = loadedNotes notes = loadedNotes
isDataLoaded = true // Mark as loaded
} }
} catch (e: Exception) {
e.printStackTrace()
} }
} }
// Simpan categories dengan debounce // Save categories when changed
LaunchedEffect(categories.size) { LaunchedEffect(categories) {
if (categories.isNotEmpty()) { if (isDataLoaded && categories.isNotEmpty()) {
delay(500) android.util.Log.d("NotesApp", "Saving ${categories.size} categories")
try { scope.launch {
dataStoreManager.saveCategories(categories) dataStoreManager.saveCategories(categories)
} catch (e: Exception) {
e.printStackTrace()
} }
} }
} }
// Simpan notes dengan debounce // Save notes when changed
LaunchedEffect(notes.size) { LaunchedEffect(notes) {
if (notes.isNotEmpty()) { if (isDataLoaded && notes.isNotEmpty()) {
delay(500) android.util.Log.d("NotesApp", "Saving ${notes.size} notes")
try { scope.launch {
dataStoreManager.saveNotes(notes) dataStoreManager.saveNotes(notes)
} catch (e: Exception) {
e.printStackTrace()
} }
} }
} }
// Provide theme colors // Save on lifecycle events
DisposableEffect(lifecycleOwner) {
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
if (event == androidx.lifecycle.Lifecycle.Event.ON_PAUSE ||
event == androidx.lifecycle.Lifecycle.Event.ON_STOP) {
android.util.Log.d("NotesApp", "Lifecycle ${event.name}: Saving data")
scope.launch {
if (categories.isNotEmpty()) {
dataStoreManager.saveCategories(categories)
}
if (notes.isNotEmpty()) {
dataStoreManager.saveNotes(notes)
}
}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
Scaffold( Scaffold(
containerColor = AppColors.Background,
topBar = { topBar = {
if (!showFullScreenNote && currentScreen != "ai") { if (!showFullScreenNote && currentScreen != "ai") {
ModernTopBar( ModernTopBar(
@ -222,7 +218,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(
@ -235,9 +236,18 @@ fun NotesApp() {
} }
}, },
containerColor = AppColors.Primary, containerColor = AppColors.Primary,
contentColor = Color.White contentColor = Color.White,
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 8.dp,
pressedElevation = 12.dp
),
modifier = Modifier.size(64.dp)
) { ) {
Icon(Icons.Default.Add, contentDescription = "Add") Icon(
Icons.Default.Add,
contentDescription = if (selectedCategory != null) "Tambah Catatan" else "Tambah Kategori",
modifier = Modifier.size(28.dp)
)
} }
} }
}, },
@ -252,8 +262,7 @@ fun NotesApp() {
onAIClick = { currentScreen = "ai" } onAIClick = { currentScreen = "ai" }
) )
} }
}, }
containerColor = AppColors.Background // SET BACKGROUND HERE
) { padding -> ) { padding ->
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
if (showFullScreenNote && fullScreenNote != null) { if (showFullScreenNote && fullScreenNote != null) {
@ -306,7 +315,7 @@ fun NotesApp() {
) { ) {
when (currentScreen) { when (currentScreen) {
"main" -> MainScreen( "main" -> MainScreen(
categories = categories.filter { !it.isDeleted }, // TAMBAHKAN FILTER INI categories = categories.filter { !it.isDeleted },
notes = notes, notes = notes,
selectedCategory = selectedCategory, selectedCategory = selectedCategory,
searchQuery = searchQuery, searchQuery = searchQuery,
@ -322,12 +331,10 @@ fun NotesApp() {
} }
}, },
onCategoryDelete = { category -> onCategoryDelete = { category ->
// UBAH: Jangan filter, tapi set isDeleted = true
categories = categories.map { categories = categories.map {
if (it.id == category.id) it.copy(isDeleted = true) if (it.id == category.id) it.copy(isDeleted = true)
else it else it
} }
// Note di dalam kategori juga di-delete
notes = notes.map { notes = notes.map {
if (it.categoryId == category.id) it.copy(isDeleted = true) if (it.categoryId == category.id) it.copy(isDeleted = true)
else it else it
@ -352,7 +359,7 @@ fun NotesApp() {
"trash" -> TrashScreen( "trash" -> TrashScreen(
notes = notes.filter { it.isDeleted }, notes = notes.filter { it.isDeleted },
categories = categories, // Pass semua categories (sudah ada yang isDeleted) categories = categories,
onRestoreNote = { note -> onRestoreNote = { note ->
notes = notes.map { notes = notes.map {
if (it.id == note.id) it.copy(isDeleted = false, isArchived = false) if (it.id == note.id) it.copy(isDeleted = false, isArchived = false)
@ -363,28 +370,24 @@ fun NotesApp() {
notes = notes.filter { it.id != note.id } notes = notes.filter { it.id != note.id }
}, },
onRestoreCategory = { category -> onRestoreCategory = { category ->
// Restore kategori
categories = categories.map { categories = categories.map {
if (it.id == category.id) it.copy(isDeleted = false) if (it.id == category.id) it.copy(isDeleted = false)
else it else it
} }
// Restore semua note di dalam kategori
notes = notes.map { notes = notes.map {
if (it.categoryId == category.id) it.copy(isDeleted = false, isArchived = false) if (it.categoryId == category.id) it.copy(isDeleted = false, isArchived = false)
else it else it
} }
}, },
onDeleteCategoryPermanent = { category -> onDeleteCategoryPermanent = { category ->
// Hapus kategori permanen
categories = categories.filter { it.id != category.id } categories = categories.filter { it.id != category.id }
// Hapus semua note di dalam kategori permanen
notes = notes.filter { it.categoryId != category.id } notes = notes.filter { it.categoryId != category.id }
} }
) )
"starred" -> StarredNotesScreen( "starred" -> StarredNotesScreen(
notes = notes, notes = notes,
categories = categories.filter { !it.isDeleted }, // FILTER categories = categories.filter { !it.isDeleted },
onNoteClick = { note -> onNoteClick = { note ->
fullScreenNote = note fullScreenNote = note
showFullScreenNote = true showFullScreenNote = true
@ -401,7 +404,7 @@ fun NotesApp() {
"archive" -> ArchiveScreen( "archive" -> ArchiveScreen(
notes = notes.filter { it.isArchived && !it.isDeleted }, notes = notes.filter { it.isArchived && !it.isDeleted },
categories = categories.filter { !it.isDeleted }, // FILTER categories = categories.filter { !it.isDeleted },
onRestore = { note -> onRestore = { note ->
notes = notes.map { notes = notes.map {
if (it.id == note.id) it.copy(isArchived = false) if (it.id == note.id) it.copy(isArchived = false)
@ -417,7 +420,7 @@ fun NotesApp() {
) )
"ai" -> AIHelperScreen( "ai" -> AIHelperScreen(
categories = categories.filter { !it.isDeleted }, // FILTER categories = categories.filter { !it.isDeleted },
notes = notes.filter { !it.isDeleted } notes = notes.filter { !it.isDeleted }
) )
} }
@ -462,13 +465,11 @@ fun NotesApp() {
categoryId = selectedCategory!!.id, categoryId = selectedCategory!!.id,
title = title, title = title,
description = description, description = description,
content = "" // Content kosong, akan diisi di EditableFullScreenNoteView content = ""
) )
} }
showNoteDialog = false showNoteDialog = false
editingNote = null editingNote = null
showNoteDialog = false
editingNote = null
}, },
onDelete = if (editingNote != null) { onDelete = if (editingNote != null) {
{ {
@ -485,11 +486,16 @@ fun NotesApp() {
} }
} }
// Drawer with theme toggle // Drawer with Animation
AnimatedVisibility( AnimatedVisibility(
visible = drawerState, visible = drawerState,
enter = fadeIn() + slideInHorizontally { -it }, enter = fadeIn() + slideInHorizontally(
exit = fadeOut() + slideOutHorizontally { -it } initialOffsetX = { -it }
),
exit = fadeOut() + slideOutHorizontally(
targetOffsetX = { -it }
),
modifier = Modifier.zIndex(100f)
) { ) {
DrawerMenu( DrawerMenu(
currentScreen = currentScreen, currentScreen = currentScreen,
@ -504,7 +510,7 @@ fun NotesApp() {
}, },
onThemeToggle = { onThemeToggle = {
isDarkTheme = !isDarkTheme isDarkTheme = !isDarkTheme
AppColors.setTheme(isDarkTheme) // UPDATE THEME AppColors.setTheme(isDarkTheme)
scope.launch { scope.launch {
dataStoreManager.saveTheme(if (isDarkTheme) "dark" else "light") dataStoreManager.saveTheme(if (isDarkTheme) "dark" else "light")
} }
@ -512,5 +518,4 @@ fun NotesApp() {
) )
} }
} }
} }