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)
* AI Agent Catatan
* 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.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.unit.dp
import java.util.UUID
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
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.data.model.Note
import com.example.notesai.data.model.Category
import com.example.notesai.util.updateWhere
import kotlinx.coroutines.delay
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 kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
colorScheme = darkColorScheme(
// Primary colors
primary = AppColors.Primary,
onPrimary = Color.White,
primaryContainer = AppColors.PrimaryContainer,
onPrimaryContainer = Color.White,
// Secondary colors
secondary = AppColors.Secondary,
onSecondary = Color.White,
secondaryContainer = AppColors.SecondaryVariant,
onSecondaryContainer = Color.White,
// Background colors
background = AppColors.Background,
onBackground = AppColors.OnBackground,
// Surface colors
surface = AppColors.Surface,
onSurface = AppColors.OnSurface,
surfaceVariant = AppColors.SurfaceVariant,
onSurfaceVariant = AppColors.OnSurfaceVariant,
// Error colors
error = AppColors.Error,
onError = Color.White,
// Other
outline = AppColors.Border,
outlineVariant = AppColors.Divider
),
typography = Typography(
// Improve typography for better readability
displayLarge = MaterialTheme.typography.displayLarge.copy(
fontWeight = FontWeight.Bold
),
@ -120,6 +96,7 @@ fun NotesApp() {
val context = LocalContext.current
val dataStoreManager = remember { DataStoreManager(context) }
val scope = rememberCoroutineScope()
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
var categories by remember { mutableStateOf(listOf<Category>()) }
var notes by remember { mutableStateOf(listOf<Note>()) }
@ -133,66 +110,85 @@ fun NotesApp() {
var showSearch by remember { mutableStateOf(false) }
var showFullScreenNote by remember { mutableStateOf(false) }
var fullScreenNote by remember { mutableStateOf<Note?>(null) }
// Theme state
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) {
dataStoreManager.themeFlow.collect { theme ->
isDarkTheme = theme == "dark"
AppColors.setTheme(isDarkTheme) // SET GLOBAL THEME
AppColors.setTheme(isDarkTheme)
}
}
// Load data dari DataStore
// Load categories ONCE
LaunchedEffect(Unit) {
try {
dataStoreManager.categoriesFlow.collect { loadedCategories ->
dataStoreManager.categoriesFlow.collect { loadedCategories ->
if (!isDataLoaded) {
android.util.Log.d("NotesApp", "Loading ${loadedCategories.size} categories")
categories = loadedCategories
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// Load notes ONCE
LaunchedEffect(Unit) {
try {
dataStoreManager.notesFlow.collect { loadedNotes ->
dataStoreManager.notesFlow.collect { loadedNotes ->
if (!isDataLoaded) {
android.util.Log.d("NotesApp", "Loading ${loadedNotes.size} notes")
notes = loadedNotes
isDataLoaded = true // Mark as loaded
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// Simpan categories dengan debounce
LaunchedEffect(categories.size) {
if (categories.isNotEmpty()) {
delay(500)
try {
// Save categories when changed
LaunchedEffect(categories) {
if (isDataLoaded && categories.isNotEmpty()) {
android.util.Log.d("NotesApp", "Saving ${categories.size} categories")
scope.launch {
dataStoreManager.saveCategories(categories)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// Simpan notes dengan debounce
LaunchedEffect(notes.size) {
if (notes.isNotEmpty()) {
delay(500)
try {
// Save notes when changed
LaunchedEffect(notes) {
if (isDataLoaded && notes.isNotEmpty()) {
android.util.Log.d("NotesApp", "Saving ${notes.size} notes")
scope.launch {
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()) {
Scaffold(
containerColor = AppColors.Background,
topBar = {
if (!showFullScreenNote && currentScreen != "ai") {
ModernTopBar(
@ -222,7 +218,12 @@ fun NotesApp() {
floatingActionButton = {
AnimatedVisibility(
visible = currentScreen == "main" && !showFullScreenNote,
enter = scaleIn() + fadeIn(),
enter = scaleIn(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
) + fadeIn(),
exit = scaleOut() + fadeOut()
) {
FloatingActionButton(
@ -235,9 +236,18 @@ fun NotesApp() {
}
},
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" }
)
}
},
containerColor = AppColors.Background // SET BACKGROUND HERE
}
) { padding ->
Box(modifier = Modifier.fillMaxSize()) {
if (showFullScreenNote && fullScreenNote != null) {
@ -306,7 +315,7 @@ fun NotesApp() {
) {
when (currentScreen) {
"main" -> MainScreen(
categories = categories.filter { !it.isDeleted }, // TAMBAHKAN FILTER INI
categories = categories.filter { !it.isDeleted },
notes = notes,
selectedCategory = selectedCategory,
searchQuery = searchQuery,
@ -322,12 +331,10 @@ fun NotesApp() {
}
},
onCategoryDelete = { category ->
// UBAH: Jangan filter, tapi set isDeleted = true
categories = categories.map {
if (it.id == category.id) it.copy(isDeleted = true)
else it
}
// Note di dalam kategori juga di-delete
notes = notes.map {
if (it.categoryId == category.id) it.copy(isDeleted = true)
else it
@ -352,7 +359,7 @@ fun NotesApp() {
"trash" -> TrashScreen(
notes = notes.filter { it.isDeleted },
categories = categories, // Pass semua categories (sudah ada yang isDeleted)
categories = categories,
onRestoreNote = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isDeleted = false, isArchived = false)
@ -363,28 +370,24 @@ fun NotesApp() {
notes = notes.filter { it.id != note.id }
},
onRestoreCategory = { category ->
// Restore kategori
categories = categories.map {
if (it.id == category.id) it.copy(isDeleted = false)
else it
}
// Restore semua note di dalam kategori
notes = notes.map {
if (it.categoryId == category.id) it.copy(isDeleted = false, isArchived = false)
else it
}
},
onDeleteCategoryPermanent = { category ->
// Hapus kategori permanen
categories = categories.filter { it.id != category.id }
// Hapus semua note di dalam kategori permanen
notes = notes.filter { it.categoryId != category.id }
}
)
"starred" -> StarredNotesScreen(
notes = notes,
categories = categories.filter { !it.isDeleted }, // FILTER
categories = categories.filter { !it.isDeleted },
onNoteClick = { note ->
fullScreenNote = note
showFullScreenNote = true
@ -401,7 +404,7 @@ fun NotesApp() {
"archive" -> ArchiveScreen(
notes = notes.filter { it.isArchived && !it.isDeleted },
categories = categories.filter { !it.isDeleted }, // FILTER
categories = categories.filter { !it.isDeleted },
onRestore = { note ->
notes = notes.map {
if (it.id == note.id) it.copy(isArchived = false)
@ -417,7 +420,7 @@ fun NotesApp() {
)
"ai" -> AIHelperScreen(
categories = categories.filter { !it.isDeleted }, // FILTER
categories = categories.filter { !it.isDeleted },
notes = notes.filter { !it.isDeleted }
)
}
@ -462,13 +465,11 @@ fun NotesApp() {
categoryId = selectedCategory!!.id,
title = title,
description = description,
content = "" // Content kosong, akan diisi di EditableFullScreenNoteView
content = ""
)
}
showNoteDialog = false
editingNote = null
showNoteDialog = false
editingNote = null
},
onDelete = if (editingNote != null) {
{
@ -485,11 +486,16 @@ fun NotesApp() {
}
}
// Drawer with theme toggle
// Drawer with Animation
AnimatedVisibility(
visible = drawerState,
enter = fadeIn() + slideInHorizontally { -it },
exit = fadeOut() + slideOutHorizontally { -it }
enter = fadeIn() + slideInHorizontally(
initialOffsetX = { -it }
),
exit = fadeOut() + slideOutHorizontally(
targetOffsetX = { -it }
),
modifier = Modifier.zIndex(100f)
) {
DrawerMenu(
currentScreen = currentScreen,
@ -504,7 +510,7 @@ fun NotesApp() {
},
onThemeToggle = {
isDarkTheme = !isDarkTheme
AppColors.setTheme(isDarkTheme) // UPDATE THEME
AppColors.setTheme(isDarkTheme)
scope.launch {
dataStoreManager.saveTheme(if (isDarkTheme) "dark" else "light")
}
@ -512,5 +518,4 @@ fun NotesApp() {
)
}
}
}
}