Memperbaiki Bug tidak tersimpan
This commit is contained in:
parent
79f7e33a5a
commit
b264f87b14
@ -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
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user