From fdf605fd48824325654a5f4b6d7a4049d900f36c Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 15 Sep 2024 21:05:51 +0300 Subject: [PATCH 01/31] Fix TaskCard Preview not showing --- .../mhss/app/ui/components/tasks/TaskCard.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/core/ui/src/main/java/com/mhss/app/ui/components/tasks/TaskCard.kt b/core/ui/src/main/java/com/mhss/app/ui/components/tasks/TaskCard.kt index 1e8ce0f7..3298a181 100644 --- a/core/ui/src/main/java/com/mhss/app/ui/components/tasks/TaskCard.kt +++ b/core/ui/src/main/java/com/mhss/app/ui/components/tasks/TaskCard.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -182,16 +183,20 @@ fun SubTasksProgressBar(modifier: Modifier = Modifier, subTasks: List) @Preview @Composable -fun LazyItemScope.TaskItemPreview() { - TaskCard( - task = Task( - title = "Task 1", - description = "Task 1 description", - dueDate = 1666999999999L, - priority = Priority.MEDIUM, - isCompleted = false - ), - onComplete = {}, - onClick = {} - ) +fun TaskItemPreview() { + LazyColumn { + item { + TaskCard( + task = Task( + title = "Task 1", + description = "Task 1 description", + dueDate = 1666999999999L, + priority = Priority.MEDIUM, + isCompleted = false + ), + onComplete = {}, + onClick = {} + ) + } + } } \ No newline at end of file From e6c1acc252d89d29e331d7d1555e2169a2cdd77a Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 15 Sep 2024 21:10:48 +0300 Subject: [PATCH 02/31] Add elevation to AI card --- .../mhss/app/presentation/integrations/IntegrationsScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/settings/presentation/src/main/java/com/mhss/app/presentation/integrations/IntegrationsScreen.kt b/settings/presentation/src/main/java/com/mhss/app/presentation/integrations/IntegrationsScreen.kt index d00a402f..382ff00c 100644 --- a/settings/presentation/src/main/java/com/mhss/app/presentation/integrations/IntegrationsScreen.kt +++ b/settings/presentation/src/main/java/com/mhss/app/presentation/integrations/IntegrationsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch @@ -51,7 +52,8 @@ fun IntegrationsScreen( modifier = Modifier .fillMaxWidth() .padding(12.dp), - shape = RoundedCornerShape(25.dp) + shape = RoundedCornerShape(25.dp), + elevation = CardDefaults.cardElevation(8.dp) ) { val provider by viewModel.getSettings( PrefsKey.IntKey(PrefsConstants.AI_PROVIDER_KEY), From 96bdc9d8df5af723ea1cbcf934c323e8899e48b7 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 15 Sep 2024 21:11:01 +0300 Subject: [PATCH 03/31] Update dependencies --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 813f0bf4..d9a1d43f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -activityCompose = "1.9.1" +activityCompose = "1.9.2" appcompat = "1.7.0" biometric = "1.2.0-alpha05" calf = "0.5.0" -composeBom = "2024.06.00" +composeBom = "2024.08.00" composeMarkdown = "0.5.0" coreKtx = "1.13.1" coroutines = "1.9.0-RC" @@ -14,8 +14,8 @@ glanceAppwidget = "1.1.0" junit = "4.13.2" junitVersion = "1.2.1" kotlinxSerializationJson = "1.7.1" -lifecycleVersion = "2.8.4" -navigationCompose = "2.8.0-beta07" +lifecycleVersion = "2.8.5" +navigationCompose = "2.8.0" room = "2.6.1" workManager = "2.9.1" androidGradlePlugin = "8.5.2" From 9bc9d8897bc15627fd73e1482b9e4117a21fa297 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 16 Sep 2024 21:34:39 +0300 Subject: [PATCH 04/31] Change TimePicker to TimeInput in DateTimeDialog --- .../ui/components/common/DateTimeDialog.kt | 21 +++++++++---------- .../mhss/app/presentation/TaskDetailScreen.kt | 14 ++++++------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt b/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt index 87bdb4af..9c4aa001 100644 --- a/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt +++ b/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt @@ -1,8 +1,8 @@ package com.mhss.app.ui.components.common import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.BasicAlertDialog @@ -17,7 +17,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.DatePicker import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TimePicker +import androidx.compose.material3.TimeInput import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberTimePickerState import androidx.compose.ui.Alignment @@ -52,19 +52,15 @@ fun DateTimeDialog( usePlatformDefaultWidth = false ) ) { - Surface( - shape = RoundedCornerShape(12.dp) - ) { + Surface(shape = RoundedCornerShape(12.dp)) { Column( horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, modifier = Modifier.padding(vertical = 8.dp, horizontal = 4.dp) ) { - AnimatedContent( - showTime, label = "", - modifier = Modifier.fillMaxWidth() - ) { isTime -> + AnimatedContent(targetState = showTime, label = "") { isTime -> if (isTime) { - TimePicker( + TimeInput( timePickerState ) } else { @@ -78,7 +74,10 @@ fun DateTimeDialog( onClick = { if (showTime) { onDatePicked( - datePickerState.selectedDateMillis?.at(timePickerState.hour, timePickerState.minute) ?: 0L + datePickerState.selectedDateMillis?.at( + timePickerState.hour, + timePickerState.minute + ) ?: initialDate ) } else showTime = true }, diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt index 9b93cd51..525fffda 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt @@ -338,14 +338,14 @@ fun TaskDetailsContent( var showDateDialog by remember { mutableStateOf(false) } + if (showDateDialog) DateTimeDialog( + onDismissRequest = { showDateDialog = false }, + initialDate = dueDate + ) { + onDueDateChange(it) + showDateDialog = false + } AnimatedVisibility(dueDateExists) { - if (showDateDialog) DateTimeDialog( - onDismissRequest = { showDateDialog = false }, - initialDate = dueDate - ) { - onDueDateChange(it) - showDateDialog = false - } Column { Row( Modifier From d45d60b1f1555e51e69f995e80072c18437227f5 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 16 Sep 2024 21:35:39 +0300 Subject: [PATCH 05/31] Fix due date not updating in AddTaskBottomSheetContent --- .../app/presentation/AddTaskBottomSheetContent.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/AddTaskBottomSheetContent.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/AddTaskBottomSheetContent.kt index 95bf8cc7..26b32417 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/AddTaskBottomSheetContent.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/AddTaskBottomSheetContent.kt @@ -20,19 +20,18 @@ import com.mhss.app.domain.model.Task import com.mhss.app.domain.model.TaskFrequency import com.mhss.app.util.date.formatDateDependingOnDay import com.mhss.app.util.date.now -import java.util.* @Composable fun AddTaskBottomSheetContent( onAddTask: (Task) -> Unit, - focusRequester: FocusRequester + focusRequester: FocusRequester, ) { val context = LocalContext.current var completed by rememberSaveable { mutableStateOf(false) } var title by rememberSaveable { mutableStateOf("") } var description by rememberSaveable { mutableStateOf("") } var priority by rememberSaveable { mutableStateOf(Priority.LOW) } - var dueDate by rememberSaveable { mutableStateOf(Calendar.getInstance()) } + var dueDate by rememberSaveable { mutableLongStateOf(now()) } var dueDateExists by rememberSaveable { mutableStateOf(false) } var recurring by rememberSaveable { mutableStateOf(false) } var frequency by rememberSaveable { mutableStateOf(TaskFrequency.DAILY) } @@ -41,7 +40,7 @@ fun AddTaskBottomSheetContent( val priorities = listOf(Priority.LOW, Priority.MEDIUM, Priority.HIGH) val formattedDate by remember { derivedStateOf { - dueDate.timeInMillis.formatDateDependingOnDay(context) + dueDate.formatDateDependingOnDay(context) } } val keyboardController = LocalSoftwareKeyboardController.current @@ -63,7 +62,7 @@ fun AddTaskBottomSheetContent( title = title, description = description, priority = priority, - dueDate = dueDate.timeInMillis, + dueDate = dueDate, dueDateExists = dueDateExists, recurring = recurring, frequency = frequency, @@ -76,7 +75,7 @@ fun AddTaskBottomSheetContent( onDescriptionChange = { description = it }, onPriorityChange = { priority = it }, onDueDateExist = { dueDateExists = it }, - onDueDateChange = { dueDate.timeInMillis = it }, + onDueDateChange = { dueDate = it }, onRecurringChange = { recurring = it }, onFrequencyChange = { frequency = it }, onFrequencyAmountChange = { frequencyAmount = it }, @@ -90,7 +89,7 @@ fun AddTaskBottomSheetContent( description = description, isCompleted = completed, priority = priority, - dueDate = if (dueDateExists) dueDate.timeInMillis else 0L, + dueDate = if (dueDateExists) dueDate else 0L, recurring = recurring, frequency = frequency, frequencyAmount = frequencyAmount, @@ -102,7 +101,7 @@ fun AddTaskBottomSheetContent( title = "" description = "" priority = Priority.LOW - dueDate = Calendar.getInstance() + dueDate = now() dueDateExists = false subTasks.clear() keyboardController?.hide() From 2a18e9698255d11abaad5cbe4df6f6468dfc97c1 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sat, 28 Sep 2024 20:45:43 +0300 Subject: [PATCH 06/31] Update dependencies versions --- .idea/kotlinc.xml | 2 +- gradle/libs.versions.toml | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 6d0ee1c2..d4b7accb 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9a1d43f..34fb7e32 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,30 +1,30 @@ [versions] activityCompose = "1.9.2" appcompat = "1.7.0" -biometric = "1.2.0-alpha05" -calf = "0.5.0" -composeBom = "2024.08.00" -composeMarkdown = "0.5.0" +biometric = "1.4.0-alpha02" +calf = "0.5.5" +composeBom = "2024.09.02" +composeMarkdown = "0.5.4" coreKtx = "1.13.1" -coroutines = "1.9.0-RC" +coroutines = "1.9.0" datastorePreferences = "1.1.1" documentfile = "1.0.1" espressoCore = "3.6.1" glanceAppwidget = "1.1.0" junit = "4.13.2" junitVersion = "1.2.1" -kotlinxSerializationJson = "1.7.1" -lifecycleVersion = "2.8.5" -navigationCompose = "2.8.0" +kotlinxSerializationJson = "1.7.3" +lifecycleVersion = "2.8.6" +navigationCompose = "2.8.1" room = "2.6.1" workManager = "2.9.1" -androidGradlePlugin = "8.5.2" -kotlin = "2.0.0" -ksp = "2.0.0-1.0.22" -koinBOM = "3.5.6" +androidGradlePlugin = "8.6.1" +kotlin = "2.0.20" +ksp = "2.0.20-1.0.25" +koinBOM = "4.0.0" koinKSP = "1.3.1" uuid = "0.8.4" -kotlinxDatetime = "0.6.0" +kotlinxDatetime = "0.6.1" material = "1.12.0" ktor = "2.3.12" squircleShape = "2.0.0" From 30407ce825c5a23e84849c1f6414a248dc15107e Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sat, 28 Sep 2024 20:50:56 +0300 Subject: [PATCH 07/31] Fix cards tonal elevation problem --- .../main/java/com/mhss/app/ui/theme/Theme.kt | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/ui/src/main/java/com/mhss/app/ui/theme/Theme.kt b/core/ui/src/main/java/com/mhss/app/ui/theme/Theme.kt index 7ed87e6c..813435e3 100644 --- a/core/ui/src/main/java/com/mhss/app/ui/theme/Theme.kt +++ b/core/ui/src/main/java/com/mhss/app/ui/theme/Theme.kt @@ -21,8 +21,15 @@ private val DarkColorPalette = darkColorScheme( background = Color.Black, onSurface = Color.White, onBackground = Color.White, - surfaceTint = Color.Transparent, - surfaceVariant = DarkGray + surfaceTint = DarkGray, + surfaceVariant = DarkGray, + surfaceContainerHighest = DarkGray, + surfaceContainerLow = DarkGray, + surfaceContainerLowest = DarkGray, + surfaceContainer = DarkGray, + surfaceContainerHigh = DarkGray, + surfaceDim = DarkGray, + surfaceBright = DarkGray ) private val LightColorPalette = lightColorScheme( @@ -31,8 +38,15 @@ private val LightColorPalette = lightColorScheme( secondary = SecondaryColor, tertiary = TertiaryColor, background = Color.White, - surfaceTint = Color.Transparent, - surfaceVariant = Color.White + surfaceTint = Color.White, + surfaceVariant = Color.White, + surfaceContainerHighest = Color.White, + surfaceContainerLow = Color.White, + surfaceContainerLowest = Color.White, + surfaceContainer = Color.White, + surfaceContainerHigh = Color.White, + surfaceDim = Color.White, + surfaceBright = Color.White ) @Composable From a422d5e0f669fcc758a11891bc589a72c16dbbcb Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sat, 28 Sep 2024 21:08:40 +0300 Subject: [PATCH 08/31] Fix note auto save and create NoteDetailsViewModel --- .gitignore | 1 + .../mhss/app/presentation/NoteDetailsEvent.kt | 15 ++ .../app/presentation/NoteDetailsScreen.kt | 149 ++++-------- .../app/presentation/NoteDetailsViewModel.kt | 218 ++++++++++++++++++ .../com/mhss/app/presentation/NoteEvent.kt | 13 +- .../com/mhss/app/presentation/NotesScreen.kt | 1 - .../mhss/app/presentation/NotesViewModel.kt | 141 +---------- .../com/mhss/app/data/BackupRepositoryImpl.kt | 3 +- 8 files changed, 280 insertions(+), 261 deletions(-) create mode 100644 notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsEvent.kt create mode 100644 notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt diff --git a/.gitignore b/.gitignore index bc16bb35..59d2e3ae 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ local.properties /app/release /.idea/appInsightsSettings.xml /.kotlin/ +/.idea/studiobot.xml diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsEvent.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsEvent.kt new file mode 100644 index 00000000..b0583930 --- /dev/null +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsEvent.kt @@ -0,0 +1,15 @@ +package com.mhss.app.presentation + +import com.mhss.app.domain.model.Note + +sealed class NoteDetailsEvent { + data class DeleteNote(val note: Note) : NoteDetailsEvent() + data object ToggleReadingMode : NoteDetailsEvent() + data class Summarize(val content: String): NoteDetailsEvent(), AiAction + data class AutoFormat(val content: String): NoteDetailsEvent(), AiAction + data class CorrectSpelling(val content: String): NoteDetailsEvent(), AiAction + data object AiResultHandled: NoteDetailsEvent() + data class ScreenOnStop(val currentNote: Note): NoteDetailsEvent() +} + +sealed interface AiAction \ No newline at end of file diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsScreen.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsScreen.kt index a7bf7544..f7e48c12 100644 --- a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsScreen.kt +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsScreen.kt @@ -5,7 +5,6 @@ package com.mhss.app.presentation import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring @@ -32,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.mhss.app.ui.R @@ -44,33 +44,28 @@ import com.mhss.app.ui.toUserMessage import com.mhss.app.util.date.formatDateDependingOnDay import dev.jeziellago.compose.markdowntext.MarkdownText import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf -@OptIn(ExperimentalMaterial3Api::class) @Composable fun NoteDetailsScreen( navController: NavHostController, noteId: Int, folderId: Int, - viewModel: NotesViewModel = koinViewModel() + viewModel: NoteDetailsViewModel = koinViewModel( + parameters = { parametersOf(noteId, folderId) } + ), ) { - LaunchedEffect(true) { - if (noteId != -1) viewModel.onEvent(NoteEvent.GetNote(noteId)) - if (folderId != -1) viewModel.onEvent(NoteEvent.GetFolder(folderId)) - } - val state = viewModel.notesUiState - val snackbarHostState = remember { SnackbarHostState() } + val state = viewModel.noteUiState var openDeleteDialog by rememberSaveable { mutableStateOf(false) } var openFolderDialog by rememberSaveable { mutableStateOf(false) } val context = LocalContext.current - var title by rememberSaveable { mutableStateOf(state.note?.title ?: "") } - var content by rememberSaveable { mutableStateOf(state.note?.content ?: "") } - var pinned by rememberSaveable { mutableStateOf(state.note?.pinned ?: false) } + var title by remember { mutableStateOf("") } + var content by remember { mutableStateOf("") } + var pinned by remember { mutableStateOf(false) } val readingMode = state.readingMode - var folder: NoteFolder? by remember { mutableStateOf(state.folder) } - val lastModified by remember(state.note) { - derivedStateOf { state.note?.updatedDate?.formatDateDependingOnDay(context) } - } + var folder: NoteFolder? by remember { mutableStateOf(null) } + var lastModified by remember { mutableStateOf("") } val wordCountString by remember { derivedStateOf { content.split(" ").size.toString() } } @@ -78,68 +73,36 @@ fun NoteDetailsScreen( val aiState = viewModel.aiState val showAiSheet = aiState.showAiSheet - LaunchedEffect(state.note) { + LaunchedEffect(state.note, state.folder) { if (state.note != null) { title = state.note.title content = state.note.content pinned = state.note.pinned - folder = state.folder + lastModified = state.note.updatedDate.formatDateDependingOnDay(context) } + folder = state.folder } - LaunchedEffect(state) { + LaunchedEffect(state.navigateUp) { if (state.navigateUp) { openDeleteDialog = false navController.navigateUp() } - if (state.error != null) { - snackbarHostState.showSnackbar( - context.getString(state.error) - ) - viewModel.onEvent(NoteEvent.ErrorDisplayed) - } - if (state.folder != folder) folder = state.folder } - BackHandler { - addOrUpdateNote( - Note( - title = title, - content = content, - pinned = pinned, - folderId = folder?.id - ), - state.note, - onNotChanged = { - navController.navigateUp() - }, - onUpdate = { - if (state.note != null) { - viewModel.onEvent( - NoteEvent.UpdateNote( - state.note.copy( - title = title, - content = content, - folderId = folder?.id - ) - ) - ) - } else { - viewModel.onEvent( - NoteEvent.AddNote( - Note( - title = title, - content = content, - pinned = pinned, - folderId = folder?.id - ) - ) + LifecycleStartEffect(Unit) { + onStopOrDispose { + viewModel.onEvent( + NoteDetailsEvent.ScreenOnStop( + Note( + title = title, + content = content, + folderId = folder?.id, + pinned = pinned ) - } - - } - ) + ) + ) + } } Scaffold( - snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { MyBrainAppBar( title = "", @@ -181,9 +144,6 @@ fun NoteDetailsScreen( } IconButton(onClick = { pinned = !pinned - if (state.note != null) { - viewModel.onEvent(NoteEvent.PinNote) - } }) { Icon( painter = if (pinned) painterResource(id = R.drawable.ic_pin_filled) @@ -194,7 +154,7 @@ fun NoteDetailsScreen( ) } IconButton(onClick = { - viewModel.onEvent(NoteEvent.ToggleReadingMode) + viewModel.onEvent(NoteDetailsEvent.ToggleReadingMode) }) { Icon( painter = painterResource(id = R.drawable.ic_read_mode), @@ -233,19 +193,19 @@ fun NoteDetailsScreen( GradientIconButton( text = stringResource(id = R.string.summarize), iconPainter = painterResource(id = R.drawable.ic_summarize), - ) { viewModel.onEvent(NoteEvent.Summarize(content)) } + ) { viewModel.onEvent(NoteDetailsEvent.Summarize(content)) } } item { GradientIconButton( text = stringResource(id = R.string.auto_format), iconPainter = painterResource(id = R.drawable.ic_auto_format), - ) { viewModel.onEvent(NoteEvent.AutoFormat(content)) } + ) { viewModel.onEvent(NoteDetailsEvent.AutoFormat(content)) } } item { GradientIconButton( text = stringResource(id = R.string.correct_spelling), iconPainter = painterResource(id = R.drawable.ic_spelling), - ) { viewModel.onEvent(NoteEvent.CorrectSpelling(content)) } + ) { viewModel.onEvent(NoteDetailsEvent.CorrectSpelling(content)) } } } } @@ -282,7 +242,7 @@ fun NoteDetailsScreen( horizontalArrangement = Arrangement.SpaceBetween ) { Text( - text = lastModified ?: "", + text = lastModified, style = MaterialTheme.typography.bodySmall.copy(color = Color.Gray) ) Text( @@ -309,7 +269,7 @@ fun NoteDetailsScreen( interactionSource = interactionSource, indication = null ) { - viewModel.onEvent(NoteEvent.AiResultHandled) + viewModel.onEvent(NoteDetailsEvent.AiResultHandled) }, contentAlignment = Alignment.BottomCenter ) { AiResultSheet( @@ -321,15 +281,15 @@ fun NoteDetailsScreen( context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("ai result", aiState.result.toString()) clipboard.setPrimaryClip(clip) - viewModel.onEvent(NoteEvent.AiResultHandled) + viewModel.onEvent(NoteDetailsEvent.AiResultHandled) }, onReplaceClick = { content = aiState.result.toString() - viewModel.onEvent(NoteEvent.AiResultHandled) + viewModel.onEvent(NoteDetailsEvent.AiResultHandled) }, onAddToNoteClick = { content = aiState.result + "\n" + content - viewModel.onEvent(NoteEvent.AiResultHandled) + viewModel.onEvent(NoteDetailsEvent.AiResultHandled) } ) } @@ -352,7 +312,7 @@ fun NoteDetailsScreen( colors = ButtonDefaults.buttonColors(containerColor = Color.Red), shape = RoundedCornerShape(25.dp), onClick = { - viewModel.onEvent(NoteEvent.DeleteNote(state.note!!)) + viewModel.onEvent(NoteDetailsEvent.DeleteNote(state.note!!)) }, ) { Text(stringResource(R.string.delete_note), color = Color.White) @@ -368,10 +328,10 @@ fun NoteDetailsScreen( } } ) - if (openFolderDialog) - BasicAlertDialog( - onDismissRequest = { openFolderDialog = false }, - ) { + if (openFolderDialog) AlertDialog( + onDismissRequest = { openFolderDialog = false }, + confirmButton = {}, + text = { Column( Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start @@ -435,31 +395,6 @@ fun NoteDetailsScreen( } } } - } + }) } -} - -private fun addOrUpdateNote( - newNote: Note, - note: Note? = null, - onNotChanged: () -> Unit = {}, - onUpdate: (Note) -> Unit, -) { - if (note != null) { - if (noteChanged(newNote, note)) - onUpdate(note) - else - onNotChanged() - } else { - onUpdate(newNote) - } -} - -private fun noteChanged( - note: Note, - newNote: Note -): Boolean { - return note.title != newNote.title || - note.content != newNote.content || - note.folderId != newNote.folderId } \ No newline at end of file diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt new file mode 100644 index 00000000..be9f1816 --- /dev/null +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt @@ -0,0 +1,218 @@ +package com.mhss.app.presentation + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mhss.app.domain.AiConstants +import com.mhss.app.domain.autoFormatNotePrompt +import com.mhss.app.domain.correctSpellingNotePrompt +import com.mhss.app.domain.model.Note +import com.mhss.app.domain.model.NoteFolder +import com.mhss.app.domain.summarizeNotePrompt +import com.mhss.app.domain.use_case.AddNoteUseCase +import com.mhss.app.domain.use_case.DeleteNoteUseCase +import com.mhss.app.domain.use_case.GetAllNoteFoldersUseCase +import com.mhss.app.domain.use_case.GetNoteFolderUseCase +import com.mhss.app.domain.use_case.GetNoteUseCase +import com.mhss.app.domain.use_case.SendAiPromptUseCase +import com.mhss.app.domain.use_case.UpdateNoteUseCase +import com.mhss.app.network.NetworkResult +import com.mhss.app.preferences.PrefsConstants +import com.mhss.app.preferences.domain.model.AiProvider +import com.mhss.app.preferences.domain.model.intPreferencesKey +import com.mhss.app.preferences.domain.model.stringPreferencesKey +import com.mhss.app.preferences.domain.use_case.GetPreferenceUseCase +import com.mhss.app.util.date.now +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.koin.android.annotation.KoinViewModel + +@KoinViewModel +class NoteDetailsViewModel( + private val getNote: GetNoteUseCase, + private val updateNote: UpdateNoteUseCase, + private val addNote: AddNoteUseCase, + private val deleteNote: DeleteNoteUseCase, + private val getPreference: GetPreferenceUseCase, + private val getAllFolders: GetAllNoteFoldersUseCase, + private val getNoteFolder: GetNoteFolderUseCase, + private val sendAiPrompt: SendAiPromptUseCase, + id: Int, + folderId: Int, +) : ViewModel() { + + var noteUiState by mutableStateOf((UiState())) + private set + + private lateinit var aiKey: String + private lateinit var aiModel: String + private lateinit var openaiURL: String + private val _aiEnabled = MutableStateFlow(false) + val aiEnabled: StateFlow = _aiEnabled + var aiState by mutableStateOf((AiState())) + private set + private var aiActionJob: Job? = null + + private val aiProvider = + getPreference(intPreferencesKey(PrefsConstants.AI_PROVIDER_KEY), AiProvider.None.id) + .map { id -> AiProvider.entries.first { it.id == id } } + .onEach { provider -> + _aiEnabled.update { provider != AiProvider.None } + when (provider) { + AiProvider.OpenAI -> { + aiKey = getPreference( + stringPreferencesKey(PrefsConstants.OPENAI_KEY), + "" + ).first() + aiModel = getPreference( + stringPreferencesKey(PrefsConstants.OPENAI_MODEL_KEY), + AiConstants.OPENAI_DEFAULT_MODEL + ).first() + openaiURL = getPreference( + stringPreferencesKey(PrefsConstants.OPENAI_URL_KEY), + AiConstants.OPENAI_BASE_URL + ).first() + } + + AiProvider.Gemini -> { + aiKey = getPreference( + stringPreferencesKey(PrefsConstants.GEMINI_KEY), + "" + ).first() + aiModel = getPreference( + stringPreferencesKey(PrefsConstants.GEMINI_MODEL_KEY), + AiConstants.GEMINI_DEFAULT_MODEL + ).first() + openaiURL = "" + } + + else -> { + aiKey = "" + aiModel = "" + openaiURL = "" + } + } + }.stateIn(viewModelScope, SharingStarted.Eagerly, AiProvider.None) + + init { + viewModelScope.launch { + val note: Note? = if (id != -1) getNote(id) else null + val folder = getNoteFolder(note?.folderId ?: folderId) + val folders = getAllFolders().first() + noteUiState = noteUiState.copy( + note = note, + folder = folder, + folders = folders, + readingMode = note != null + ) + } + } + + fun onEvent(event: NoteDetailsEvent) { + when (event) { + is NoteDetailsEvent.ScreenOnStop -> viewModelScope.launch { + if (noteUiState.note == null) { + if (event.currentNote.title.isNotBlank() || event.currentNote.content.isNotBlank()) { + addNote( + event.currentNote.copy( + createdDate = now(), + updatedDate = now() + ) + ) + } + } else if (noteChanged(noteUiState.note!!, event.currentNote)) { + val newNote = noteUiState.note!!.copy( + title = event.currentNote.title, + content = event.currentNote.content, + folderId = event.currentNote.folderId, + pinned = event.currentNote.pinned, + updatedDate = now() + ) + updateNote(newNote) + noteUiState = noteUiState.copy(note = newNote) + } + } + + is NoteDetailsEvent.DeleteNote -> viewModelScope.launch { + deleteNote(event.note) + noteUiState = noteUiState.copy(navigateUp = true) + } + + NoteDetailsEvent.ToggleReadingMode -> noteUiState = + noteUiState.copy(readingMode = !noteUiState.readingMode) + + is AiAction -> aiActionJob = viewModelScope.launch { + val prompt = when (event) { + is NoteDetailsEvent.Summarize -> event.content.summarizeNotePrompt + is NoteDetailsEvent.AutoFormat -> event.content.autoFormatNotePrompt + is NoteDetailsEvent.CorrectSpelling -> event.content.correctSpellingNotePrompt + } + aiState = aiState.copy( + loading = true, + showAiSheet = true, + result = null, + error = null + ) + val result = sendAiPrompt(prompt) + aiState = when (result) { + is NetworkResult.Success -> aiState.copy( + loading = false, + result = result.data, + error = null + ) + + is NetworkResult.Failure -> aiState.copy(error = result, loading = false) + } + } + + NoteDetailsEvent.AiResultHandled -> { + aiActionJob?.cancel() + aiActionJob = null + aiState = aiState.copy(showAiSheet = false) + } + } + } + + private suspend fun sendAiPrompt(prompt: String) = sendAiPrompt( + prompt, + aiKey, + aiModel, + aiProvider.value, + openaiURL + ) + + private fun noteChanged( + note: Note, + newNote: Note, + ): Boolean { + return note.title != newNote.title || + note.content != newNote.content || + note.folderId != newNote.folderId || + note.pinned != newNote.pinned + } + + data class UiState( + val note: Note? = null, + val navigateUp: Boolean = false, + val readingMode: Boolean = false, + val folders: List = emptyList(), + val folder: NoteFolder? = null, + ) + + data class AiState( + val loading: Boolean = false, + val result: String? = null, + val error: NetworkResult.Failure? = null, + val showAiSheet: Boolean = false, + ) +} \ No newline at end of file diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteEvent.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteEvent.kt index 66bcc6e9..0c37c7c7 100644 --- a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteEvent.kt +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteEvent.kt @@ -5,25 +5,14 @@ import com.mhss.app.preferences.domain.model.Order import com.mhss.app.ui.ItemView sealed class NoteEvent { - data class GetNote(val noteId: Int) : NoteEvent() data class AddNote(val note: Note) : NoteEvent() data class SearchNotes(val query: String) : NoteEvent() data class UpdateOrder(val order: Order) : NoteEvent() data class UpdateView(val view: ItemView) : NoteEvent() - data class UpdateNote(val note: Note) : NoteEvent() - data class DeleteNote(val note: Note) : NoteEvent() - data object PinNote : NoteEvent() - data object ToggleReadingMode : NoteEvent() data object ErrorDisplayed: NoteEvent() data class CreateFolder(val folder: NoteFolder): NoteEvent() data class DeleteFolder(val folder: NoteFolder): NoteEvent() data class UpdateFolder(val folder: NoteFolder): NoteEvent() data class GetFolderNotes(val id: Int): NoteEvent() data class GetFolder(val id: Int): NoteEvent() - data class Summarize(val content: String): NoteEvent(), AiAction - data class AutoFormat(val content: String): NoteEvent(), AiAction - data class CorrectSpelling(val content: String): NoteEvent(), AiAction - data object AiResultHandled: NoteEvent() -} - -sealed interface AiAction \ No newline at end of file +} \ No newline at end of file diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NotesScreen.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NotesScreen.kt index 54367885..0caad1a5 100644 --- a/notes/presentation/src/main/java/com/mhss/app/presentation/NotesScreen.kt +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NotesScreen.kt @@ -41,7 +41,6 @@ import com.mhss.app.ui.navigation.Screen import com.mhss.app.ui.titleRes import org.koin.androidx.compose.koinViewModel -@OptIn(ExperimentalMaterial3Api::class) @Composable fun NotesScreen( navController: NavHostController, diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NotesViewModel.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NotesViewModel.kt index b608da68..62734443 100644 --- a/notes/presentation/src/main/java/com/mhss/app/presentation/NotesViewModel.kt +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NotesViewModel.kt @@ -7,17 +7,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.mhss.app.preferences.PrefsConstants import com.mhss.app.ui.R -import com.mhss.app.domain.AiConstants -import com.mhss.app.domain.autoFormatNotePrompt -import com.mhss.app.domain.correctSpellingNotePrompt import com.mhss.app.domain.model.* -import com.mhss.app.domain.summarizeNotePrompt import com.mhss.app.domain.use_case.* -import com.mhss.app.preferences.domain.model.AiProvider import com.mhss.app.preferences.domain.model.Order import com.mhss.app.preferences.domain.model.OrderType import com.mhss.app.preferences.domain.model.intPreferencesKey -import com.mhss.app.preferences.domain.model.stringPreferencesKey import com.mhss.app.preferences.domain.model.toInt import com.mhss.app.preferences.domain.model.toOrder import com.mhss.app.preferences.domain.use_case.GetPreferenceUseCase @@ -29,16 +23,12 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import org.koin.android.annotation.KoinViewModel -import com.mhss.app.network.NetworkResult @KoinViewModel class NotesViewModel( private val folderlessNotes: GetAllFolderlessNotesUseCase, - private val getNote: GetNoteUseCase, - private val updateNote: UpdateNoteUseCase, private val addNote: AddNoteUseCase, private val searchNotes: SearchNotesUseCase, - private val deleteNote: DeleteNoteUseCase, private val getPreference: GetPreferenceUseCase, private val savePreference: SavePreferenceUseCase, private val getAllFolders: GetAllNoteFoldersUseCase, @@ -47,7 +37,6 @@ class NotesViewModel( private val updateFolder: UpdateNoteFolderUseCase, private val getFolderNotes: GetNotesByFolderUseCase, private val getNoteFolder: GetNoteFolderUseCase, - private val sendAiPrompt: SendAiPromptUseCase ) : ViewModel() { var notesUiState by mutableStateOf((UiState())) @@ -56,57 +45,6 @@ class NotesViewModel( private var getNotesJob: Job? = null private var getFolderNotesJob: Job? = null - private lateinit var aiKey: String - private lateinit var aiModel: String - private lateinit var openaiURL: String - private val _aiEnabled = MutableStateFlow(false) - val aiEnabled: StateFlow = _aiEnabled - var aiState by mutableStateOf((AiState())) - private set - private var aiActionJob: Job? = null - - private val aiProvider = - getPreference(intPreferencesKey(PrefsConstants.AI_PROVIDER_KEY), AiProvider.None.id) - .map { id -> AiProvider.entries.first { it.id == id } } - .onEach { provider -> - _aiEnabled.update { provider != AiProvider.None } - when (provider) { - AiProvider.OpenAI -> { - aiKey = getPreference( - stringPreferencesKey(PrefsConstants.OPENAI_KEY), - "" - ).first() - aiModel = getPreference( - stringPreferencesKey(PrefsConstants.OPENAI_MODEL_KEY), - AiConstants.OPENAI_DEFAULT_MODEL - ).first() - openaiURL = getPreference( - stringPreferencesKey(PrefsConstants.OPENAI_URL_KEY), - AiConstants.OPENAI_BASE_URL - ).first() - } - - AiProvider.Gemini -> { - aiKey = getPreference( - stringPreferencesKey(PrefsConstants.GEMINI_KEY), - "" - ).first() - aiModel = getPreference( - stringPreferencesKey(PrefsConstants.GEMINI_MODEL_KEY), - AiConstants.GEMINI_DEFAULT_MODEL - ).first() - openaiURL = "" - } - - else -> { - aiKey = "" - aiModel = "" - openaiURL = "" - } - } - }.stateIn(viewModelScope, SharingStarted.Eagerly, AiProvider.None) - - init { viewModelScope.launch { combine( @@ -132,43 +70,20 @@ class NotesViewModel( fun onEvent(event: NoteEvent) { when (event) { is NoteEvent.AddNote -> viewModelScope.launch { - notesUiState = if (event.note.title.isBlank() && event.note.content.isBlank()) { - notesUiState.copy(navigateUp = true) - } else { + if (event.note.title.isNotBlank() || event.note.content.isNotBlank()) { addNote( event.note.copy( createdDate = now(), updatedDate = now() ) ) - notesUiState.copy(navigateUp = true) } } - is NoteEvent.DeleteNote -> viewModelScope.launch { - deleteNote(event.note) - notesUiState = notesUiState.copy(navigateUp = true) - } - - is NoteEvent.GetNote -> viewModelScope.launch { - val note = getNote(event.noteId) - val folder = getAllFolders().first().firstOrNull { it.id == note.folderId } - notesUiState = notesUiState.copy(note = note, folder = folder, readingMode = true) - } - is NoteEvent.SearchNotes -> viewModelScope.launch { notesUiState = notesUiState.copy(searchNotes = searchNotes(event.query)) } - is NoteEvent.UpdateNote -> viewModelScope.launch { - notesUiState = if (event.note.title.isBlank() && event.note.content.isBlank()) - notesUiState.copy(error = R.string.error_empty_note) - else { - updateNote(event.note.copy(updatedDate = now())) - notesUiState.copy(navigateUp = true) - } - } - is NoteEvent.UpdateOrder -> viewModelScope.launch { savePreference( intPreferencesKey(PrefsConstants.NOTES_ORDER_KEY), @@ -180,13 +95,6 @@ class NotesViewModel( notesUiState = notesUiState.copy(error = null) } - NoteEvent.ToggleReadingMode -> notesUiState = - notesUiState.copy(readingMode = !notesUiState.readingMode) - - is NoteEvent.PinNote -> viewModelScope.launch { - updateNote(notesUiState.note?.copy(pinned = !notesUiState.note?.pinned!!)!!) - } - is NoteEvent.UpdateView -> viewModelScope.launch { savePreference( intPreferencesKey(PrefsConstants.NOTE_VIEW_KEY), @@ -232,68 +140,21 @@ class NotesViewModel( val folder = getNoteFolder(event.id) notesUiState = notesUiState.copy(folder = folder) } - - is AiAction -> aiActionJob = viewModelScope.launch { - val prompt = when (event) { - is NoteEvent.Summarize -> event.content.summarizeNotePrompt - is NoteEvent.AutoFormat -> event.content.autoFormatNotePrompt - is NoteEvent.CorrectSpelling -> event.content.correctSpellingNotePrompt - } - aiState = aiState.copy( - loading = true, - showAiSheet = true, - result = null, - error = null - ) - val result = sendAiPrompt(prompt) - aiState = when (result) { - is NetworkResult.Success -> aiState.copy( - loading = false, - result = result.data, - error = null - ) - - is NetworkResult.Failure -> aiState.copy(error = result, loading = false) - } - } - - NoteEvent.AiResultHandled -> { - aiActionJob?.cancel() - aiActionJob = null - aiState = aiState.copy(showAiSheet = false) - } } } - private suspend fun sendAiPrompt(prompt: String) = sendAiPrompt( - prompt, - aiKey, - aiModel, - aiProvider.value, - openaiURL - ) - data class UiState( val notes: List = emptyList(), - val note: Note? = null, val notesOrder: Order = Order.DateModified(OrderType.ASC), val error: Int? = null, val noteView: ItemView = ItemView.LIST, val navigateUp: Boolean = false, - val readingMode: Boolean = false, val searchNotes: List = emptyList(), val folders: List = emptyList(), val folderNotes: List = emptyList(), val folder: NoteFolder? = null ) - data class AiState( - val loading: Boolean = false, - val result: String? = null, - val error: NetworkResult.Failure? = null, - val showAiSheet: Boolean = false - ) - private fun getFolderlessNotes(order: Order) { getNotesJob?.cancel() getNotesJob = folderlessNotes(order) diff --git a/settings/data/src/main/java/com/mhss/app/data/BackupRepositoryImpl.kt b/settings/data/src/main/java/com/mhss/app/data/BackupRepositoryImpl.kt index d3e0226c..7990a43f 100644 --- a/settings/data/src/main/java/com/mhss/app/data/BackupRepositoryImpl.kt +++ b/settings/data/src/main/java/com/mhss/app/data/BackupRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.mhss.app.data import android.content.Context +import android.util.Log import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import androidx.room.withTransaction @@ -94,7 +95,7 @@ class BackupRepositoryImpl( database.withTransaction { val newNoteFolderIds = database.noteDao().insertNoteFolders(backupData.noteFolders.withoutIds()) val notes = if (newNoteFolderIds.size != oldNoteFolderIdsMap.keys.size) { - println("BackupRepositoryImpl.importDatabase: New folder count (${newNoteFolderIds.size}) does not match old folder count. {${oldNoteFolderIdsMap.keys.size})") + Log.d("BackupRepositoryImpl.importDatabase","New folder count (${newNoteFolderIds.size}) does not match old folder count. {${oldNoteFolderIdsMap.keys.size})") backupData.notes.withoutIds() } else backupData.notes.map { note -> note.copy( From 87eab080c5b3311a6985ec685dcf492c49a0c509 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 29 Sep 2024 21:39:39 +0300 Subject: [PATCH 09/31] Update DateTimeDialog UI --- .../ui/components/common/DateTimeDialog.kt | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt b/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt index 9c4aa001..f865534f 100644 --- a/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt +++ b/core/ui/src/main/java/com/mhss/app/ui/components/common/DateTimeDialog.kt @@ -1,8 +1,8 @@ package com.mhss.app.ui.components.common import androidx.compose.animation.AnimatedContent -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.BasicAlertDialog @@ -13,17 +13,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource -import androidx.compose.material3.Button import androidx.compose.material3.DatePicker import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TimeInput import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberTimePickerState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogProperties import com.mhss.app.ui.R import com.mhss.app.util.date.at import com.mhss.app.util.date.hour @@ -34,7 +33,7 @@ import com.mhss.app.util.date.minute fun DateTimeDialog( initialDate: Long, onDismissRequest: () -> Unit, - onDatePicked: (Long) -> Unit + onDatePicked: (Long) -> Unit, ) { val datePickerState = rememberDatePickerState( initialSelectedDateMillis = initialDate @@ -48,20 +47,19 @@ fun DateTimeDialog( } BasicAlertDialog( onDismissRequest = onDismissRequest, - properties = DialogProperties( - usePlatformDefaultWidth = false - ) + modifier = Modifier.fillMaxWidth().padding(8.dp) ) { - Surface(shape = RoundedCornerShape(12.dp)) { + Surface( + shape = RoundedCornerShape(20.dp) + ) { Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.padding(vertical = 8.dp, horizontal = 4.dp) + horizontalAlignment = Alignment.CenterHorizontally ) { AnimatedContent(targetState = showTime, label = "") { isTime -> if (isTime) { TimeInput( - timePickerState + timePickerState, + modifier = Modifier.padding(12.dp) ) } else { DatePicker( @@ -70,7 +68,7 @@ fun DateTimeDialog( ) } } - Button( + TextButton( onClick = { if (showTime) { onDatePicked( @@ -86,5 +84,6 @@ fun DateTimeDialog( } } } + } } \ No newline at end of file From 9ab3cdcff547c5d4ee98df971318845216c7c6a7 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 29 Sep 2024 21:40:06 +0300 Subject: [PATCH 10/31] Decrease SpacesScreen min column width --- .../java/com/mhss/app/mybrain/presentation/main/SpacesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/SpacesScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/SpacesScreen.kt index 0eb01a59..1561411c 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/SpacesScreen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/SpacesScreen.kt @@ -43,7 +43,7 @@ fun SpacesScreen( ) { paddingValues -> val surfaceVariant = MaterialTheme.colorScheme.surfaceVariant LazyVerticalGrid( - columns = GridCells.Adaptive(150.dp), + columns = GridCells.Adaptive(140.dp), modifier = Modifier.padding(paddingValues), contentPadding = PaddingValues( top = 10.dp, From 6b6c21d459bfa540c9baa60a0a83ba48bcd3b288 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 2 Oct 2024 20:21:32 +0300 Subject: [PATCH 11/31] Replace BackHandler auto save with onStopOrDispose in TaskDetailScreen --- .../presentation/main/DashboardEvent.kt | 2 +- .../presentation/main/DashboardScreen.kt | 4 +- .../presentation/main/MainViewModel.kt | 8 +- .../mhss/app/notification/AlarmReceiver.kt | 2 +- .../app/domain/use_case/AddTaskUseCase.kt | 2 +- .../app/domain/use_case/UpdateTaskUseCase.kt | 7 +- .../mhss/app/presentation/TaskDetailScreen.kt | 129 +++++++----------- .../mhss/app/presentation/TaskDetailsEvent.kt | 9 ++ .../app/presentation/TaskDetailsViewModel.kt | 94 +++++++++++++ .../com/mhss/app/presentation/TaskEvent.kt | 3 - .../app/presentation/TasksDashboardWidget.kt | 6 +- .../mhss/app/presentation/TasksViewModel.kt | 45 ------ 12 files changed, 167 insertions(+), 144 deletions(-) create mode 100644 tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsEvent.kt create mode 100644 tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsViewModel.kt diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardEvent.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardEvent.kt index 988eb7c5..cf54ebd7 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardEvent.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardEvent.kt @@ -5,6 +5,6 @@ import com.mhss.app.domain.model.Task sealed class DashboardEvent { data class ReadPermissionChanged(val hasPermission: Boolean) : DashboardEvent() - data class UpdateTask(val task: Task) : DashboardEvent() + data class CompleteTask(val task: Task, val isCompleted: Boolean) : DashboardEvent() data object InitAll : DashboardEvent() } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardScreen.kt index 18b8f19f..cd4278d5 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardScreen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/DashboardScreen.kt @@ -65,8 +65,8 @@ fun DashboardScreen( .fillMaxWidth() .aspectRatio(1.5f), tasks = viewModel.uiState.dashBoardTasks, - onCheck = { - viewModel.onDashboardEvent(DashboardEvent.UpdateTask(it)) + onCheck = { task, completed -> + viewModel.onDashboardEvent(DashboardEvent.CompleteTask(task, completed)) }, onTaskClick = { navController.navigate( diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt index 67babc5d..1497d06d 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt @@ -11,8 +11,8 @@ import com.mhss.app.domain.model.Task import com.mhss.app.domain.use_case.GetAllEntriesUseCase import com.mhss.app.domain.use_case.GetAllEventsUseCase import com.mhss.app.domain.use_case.GetAllTasksUseCase -import com.mhss.app.domain.use_case.UpdateTaskUseCase import com.mhss.app.domain.model.DiaryEntry +import com.mhss.app.domain.use_case.UpdateTaskCompletedUseCase import com.mhss.app.preferences.domain.model.* import com.mhss.app.preferences.domain.use_case.GetPreferenceUseCase import com.mhss.app.preferences.domain.use_case.SavePreferenceUseCase @@ -34,7 +34,7 @@ class MainViewModel( private val savePreference: SavePreferenceUseCase, private val getAllTasks: GetAllTasksUseCase, private val getAllEntriesUseCase: GetAllEntriesUseCase, - private val updateTask: UpdateTaskUseCase, + private val completeTask: UpdateTaskCompletedUseCase, private val getAllEventsUseCase: GetAllEventsUseCase ) : ViewModel() { @@ -56,8 +56,8 @@ class MainViewModel( if (event.hasPermission) getCalendarEvents() } - is DashboardEvent.UpdateTask -> viewModelScope.launch { - updateTask(event.task, event.task) + is DashboardEvent.CompleteTask -> viewModelScope.launch { + completeTask(event.task.id, event.isCompleted) } DashboardEvent.InitAll -> collectDashboardData() } diff --git a/core/notification/src/main/java/com/mhss/app/notification/AlarmReceiver.kt b/core/notification/src/main/java/com/mhss/app/notification/AlarmReceiver.kt index 16a88664..2ab90657 100644 --- a/core/notification/src/main/java/com/mhss/app/notification/AlarmReceiver.kt +++ b/core/notification/src/main/java/com/mhss/app/notification/AlarmReceiver.kt @@ -56,7 +56,7 @@ class AlarmReceiver : BroadcastReceiver(), KoinComponent { val newTask = task.copy( dueDate = calendar.timeInMillis, ) - updateTaskUseCase(newTask, task) + updateTaskUseCase(newTask, true) addAlarmUseCase(Alarm(newTask.id, newTask.dueDate)) } } diff --git a/tasks/domain/src/main/java/com/mhss/app/domain/use_case/AddTaskUseCase.kt b/tasks/domain/src/main/java/com/mhss/app/domain/use_case/AddTaskUseCase.kt index be15f45b..0b5150f8 100644 --- a/tasks/domain/src/main/java/com/mhss/app/domain/use_case/AddTaskUseCase.kt +++ b/tasks/domain/src/main/java/com/mhss/app/domain/use_case/AddTaskUseCase.kt @@ -24,7 +24,7 @@ class AddTaskUseCase( task.dueDate, ) ) - if (!success) updateTask(task.copy(id = id, dueDate = 0L), task) + if (!success) updateTask(task.copy(id = id, dueDate = 0L), false) success } else true } diff --git a/tasks/domain/src/main/java/com/mhss/app/domain/use_case/UpdateTaskUseCase.kt b/tasks/domain/src/main/java/com/mhss/app/domain/use_case/UpdateTaskUseCase.kt index d8dea293..6e006ed2 100644 --- a/tasks/domain/src/main/java/com/mhss/app/domain/use_case/UpdateTaskUseCase.kt +++ b/tasks/domain/src/main/java/com/mhss/app/domain/use_case/UpdateTaskUseCase.kt @@ -15,18 +15,17 @@ class UpdateTaskUseCase( private val deleteAlarm: DeleteAlarmUseCase, private val widgetUpdater: WidgetUpdater ) { - suspend operator fun invoke(task: Task, oldTask: Task): Boolean { + suspend operator fun invoke(task: Task, updateDueDate: Boolean): Boolean { tasksRepository.updateTask(task) widgetUpdater.updateAll(WidgetUpdater.WidgetType.Tasks) - return if (task.dueDate != oldTask.dueDate) { + return if (updateDueDate) { if (task.dueDate != 0L) { - val scheduleSuccess = addAlarm( + addAlarm( Alarm( task.id, task.dueDate ) ) - scheduleSuccess } else { deleteAlarm(task.id) true diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt index 525fffda..03207890 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailScreen.kt @@ -1,6 +1,5 @@ package com.mhss.app.presentation -import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -25,6 +24,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.navigation.NavHostController import com.mhss.app.ui.R import com.mhss.app.domain.model.Priority @@ -39,19 +39,17 @@ import com.mhss.app.ui.components.common.MyBrainAppBar import com.mhss.app.ui.components.tasks.TaskCheckBox import com.mhss.app.ui.titleRes import com.mhss.app.util.date.formatDateDependingOnDay +import com.mhss.app.util.date.now import com.mhss.app.util.permissions.rememberPermissionState import org.koin.androidx.compose.koinViewModel -import java.util.* +import org.koin.core.parameter.parametersOf @Composable fun TaskDetailScreen( navController: NavHostController, taskId: Int, - viewModel: TasksViewModel = koinViewModel() + viewModel: TaskDetailsViewModel = koinViewModel(parameters = { parametersOf(taskId) }), ) { - LaunchedEffect(true) { - viewModel.onEvent(TaskEvent.GetTask(taskId)) - } val alarmPermissionState = rememberPermissionState(Permission.SCHEDULE_ALARMS) val uiState = viewModel.taskDetailsUiState val snackbarHostState = remember { @@ -60,15 +58,15 @@ fun TaskDetailScreen( var openDialog by rememberSaveable { mutableStateOf(false) } val context = LocalContext.current - var title by rememberSaveable { mutableStateOf("") } - var description by rememberSaveable { mutableStateOf("") } - var priority by rememberSaveable { mutableStateOf(Priority.LOW) } - var dueDate by rememberSaveable { mutableLongStateOf(0L) } - var recurring by rememberSaveable { mutableStateOf(false) } - var frequency by rememberSaveable { mutableStateOf(TaskFrequency.DAILY) } - var frequencyAmount by rememberSaveable { mutableIntStateOf(1) } - var dueDateExists by rememberSaveable { mutableStateOf(false) } - var completed by rememberSaveable { mutableStateOf(false) } + var title by remember { mutableStateOf("") } + var description by remember { mutableStateOf("") } + var priority by remember { mutableStateOf(Priority.LOW) } + var dueDate by remember { mutableLongStateOf(0L) } + var recurring by remember { mutableStateOf(false) } + var frequency by remember { mutableStateOf(TaskFrequency.DAILY) } + var frequencyAmount by remember { mutableIntStateOf(1) } + var dueDateExists by remember { mutableStateOf(false) } + var completed by remember { mutableStateOf(false) } val subTasks = remember { mutableStateListOf() } val priorities = listOf(Priority.LOW, Priority.MEDIUM, Priority.HIGH) val formattedDate by remember { @@ -78,23 +76,27 @@ fun TaskDetailScreen( } LaunchedEffect(uiState.task) { - title = uiState.task.title - description = uiState.task.description - priority = uiState.task.priority - dueDate = uiState.task.dueDate - dueDateExists = uiState.task.dueDate != 0L - completed = uiState.task.isCompleted - recurring = uiState.task.recurring - frequency = uiState.task.frequency - frequencyAmount = uiState.task.frequencyAmount - subTasks.addAll(uiState.task.subTasks) + if (uiState.task != null) { + title = uiState.task.title + description = uiState.task.description + priority = uiState.task.priority + dueDate = uiState.task.dueDate + dueDateExists = uiState.task.dueDate != 0L + completed = uiState.task.isCompleted + recurring = uiState.task.recurring + frequency = uiState.task.frequency + frequencyAmount = uiState.task.frequencyAmount + subTasks.clear() + subTasks.addAll(uiState.task.subTasks) + } } - LaunchedEffect(uiState) { + LaunchedEffect(uiState.navigateUp, uiState.error, uiState.errorAlarm) { if (uiState.navigateUp) { openDialog = false navController.navigateUp() } if (uiState.error != null) { + if (uiState.errorAlarm) dueDateExists = false val snackbarResult = snackbarHostState.showSnackbar( context.getString(uiState.error), if (uiState.errorAlarm) context.getString(R.string.grant_permission) else null @@ -102,29 +104,27 @@ fun TaskDetailScreen( if (snackbarResult == SnackbarResult.ActionPerformed) { alarmPermissionState.launchRequest() } - viewModel.onEvent(TaskEvent.ErrorDisplayed) + viewModel.onEvent(TaskDetailsEvent.ErrorDisplayed) } } - BackHandler { - updateTaskIfChanged( - uiState.task, - uiState.task.copy( - title = title, - description = description, - dueDate = if (dueDateExists) dueDate else 0L, - priority = priority, - subTasks = subTasks, - recurring = recurring, - frequency = frequency, - frequencyAmount = frequencyAmount - ), - onNotChanged = { - navController.navigateUp() - }, - onUpdate = { - viewModel.onEvent(TaskEvent.UpdateTask(it, dueDate != uiState.task.dueDate)) - } - ) + LifecycleStartEffect(Unit) { + onStopOrDispose { + viewModel.onEvent( + TaskDetailsEvent.ScreenOnStop( + Task( + title = title, + description = description, + isCompleted = completed, + dueDate = if (dueDateExists) dueDate else 0L, + priority = priority, + subTasks = subTasks, + recurring = recurring, + frequency = frequency, + frequencyAmount = frequencyAmount + ) + ) + ) + } } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, @@ -161,7 +161,7 @@ fun TaskDetailScreen( onPriorityChange = { priority = it }, onDueDateExist = { dueDateExists = it - if (it) dueDate = Calendar.getInstance().timeInMillis + if (it) dueDate = now() }, onDueDateChange = { dueDate = it }, onRecurringChange = { recurring = it }, @@ -169,12 +169,6 @@ fun TaskDetailScreen( onFrequencyAmountChange = { frequencyAmount = it }, onComplete = { completed = it - viewModel.onEvent( - TaskEvent.CompleteTask( - uiState.task, - it - ) - ) } ) } @@ -187,7 +181,7 @@ fun TaskDetailScreen( Text( stringResource( R.string.delete_task_confirmation_message, - uiState.task.title + uiState.task?.title ?: "Untitled" ) ) }, @@ -196,7 +190,7 @@ fun TaskDetailScreen( colors = ButtonDefaults.buttonColors(containerColor = Color.Red), shape = RoundedCornerShape(25.dp), onClick = { - viewModel.onEvent(TaskEvent.DeleteTask(uiState.task)) + viewModel.onEvent(TaskDetailsEvent.DeleteTask) }, ) { Text(stringResource(R.string.delete_task), color = Color.White) @@ -465,29 +459,6 @@ fun PriorityTabRow( } } -private fun updateTaskIfChanged( - task: Task, - newTask: Task, - onNotChanged: () -> Unit = {}, - onUpdate: (Task) -> Unit, -) { - if (taskChanged(task, newTask)) onUpdate(newTask) else onNotChanged() -} - -private fun taskChanged( - task: Task, - newTask: Task -): Boolean { - return task.title != newTask.title || - task.description != newTask.description || - task.dueDate != newTask.dueDate || - task.priority != newTask.priority || - task.subTasks != newTask.subTasks || - task.recurring != newTask.recurring || - task.frequency != newTask.frequency || - task.frequencyAmount != newTask.frequencyAmount -} - @Composable fun DropDownItem( modifier: Modifier = Modifier, diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsEvent.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsEvent.kt new file mode 100644 index 00000000..4e347cf7 --- /dev/null +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsEvent.kt @@ -0,0 +1,9 @@ +package com.mhss.app.presentation + +import com.mhss.app.domain.model.Task + +sealed class TaskDetailsEvent { + data class ScreenOnStop(val task: Task): TaskDetailsEvent() + data object DeleteTask : TaskDetailsEvent() + data object ErrorDisplayed: TaskDetailsEvent() +} \ No newline at end of file diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsViewModel.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsViewModel.kt new file mode 100644 index 00000000..14ae3473 --- /dev/null +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDetailsViewModel.kt @@ -0,0 +1,94 @@ +package com.mhss.app.presentation + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mhss.app.domain.model.Task +import com.mhss.app.domain.use_case.DeleteTaskUseCase +import com.mhss.app.domain.use_case.GetTaskByIdUseCase +import com.mhss.app.domain.use_case.UpdateTaskUseCase +import com.mhss.app.util.date.now +import kotlinx.coroutines.launch +import org.koin.android.annotation.KoinViewModel + +@KoinViewModel +class TaskDetailsViewModel( + private val getTask: GetTaskByIdUseCase, + private val updateTask: UpdateTaskUseCase, + private val deleteTask: DeleteTaskUseCase, + taskId: Int +) : ViewModel() { + + var taskDetailsUiState by mutableStateOf(TaskDetailsUiState()) + private set + + init { + viewModelScope.launch { + val task = getTask(taskId) + taskDetailsUiState = taskDetailsUiState.copy( + task = task + ) + } + } + + fun onEvent(event: TaskDetailsEvent) { + when (event) { + + TaskDetailsEvent.ErrorDisplayed -> { + taskDetailsUiState = taskDetailsUiState.copy(error = null, errorAlarm = false) + } + + is TaskDetailsEvent.ScreenOnStop -> viewModelScope.launch { + if (taskChanged(taskDetailsUiState.task!!, event.task)) { + val newTask = taskDetailsUiState.task!!.copy( + title = event.task.title.ifBlank { "Untitled" }, + description = event.task.description, + dueDate = event.task.dueDate, + priority = event.task.priority, + subTasks = event.task.subTasks, + recurring = event.task.recurring, + frequency = event.task.frequency, + frequencyAmount = event.task.frequencyAmount, + isCompleted = event.task.isCompleted, + updatedDate = now() + ) + updateTask( + newTask, + event.task.dueDate != taskDetailsUiState.task!!.dueDate + ) + taskDetailsUiState = taskDetailsUiState.copy(task = newTask) + } + } + + is TaskDetailsEvent.DeleteTask -> viewModelScope.launch { + deleteTask(taskDetailsUiState.task!!) + taskDetailsUiState = taskDetailsUiState.copy(navigateUp = true) + } + } + } + + data class TaskDetailsUiState( + val task: Task? = null, + val navigateUp: Boolean = false, + val error: Int? = null, + val errorAlarm: Boolean = false, + ) + + private fun taskChanged( + task: Task, + newTask: Task + ): Boolean { + return task.title != newTask.title || + task.description != newTask.description || + task.dueDate != newTask.dueDate || + task.isCompleted != newTask.isCompleted || + task.priority != newTask.priority || + task.subTasks != newTask.subTasks || + task.recurring != newTask.recurring || + task.frequency != newTask.frequency || + task.frequencyAmount != newTask.frequencyAmount + } + +} \ No newline at end of file diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskEvent.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskEvent.kt index 27e950ae..891b5f5a 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskEvent.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskEvent.kt @@ -5,12 +5,9 @@ import com.mhss.app.preferences.domain.model.Order sealed class TaskEvent { data class CompleteTask(val task: Task, val complete: Boolean) : TaskEvent() - data class GetTask(val taskId: Int) : TaskEvent() data class AddTask(val task: Task) : TaskEvent() data class SearchTasks(val query: String) : TaskEvent() data class UpdateOrder(val order: Order) : TaskEvent() data class ShowCompletedTasks(val showCompleted: Boolean) : TaskEvent() - data class UpdateTask(val task: Task, val dueDateUpdated: Boolean) : TaskEvent() - data class DeleteTask(val task: Task) : TaskEvent() data object ErrorDisplayed: TaskEvent() } \ No newline at end of file diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt index 30f56cdf..3d826f58 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt @@ -1,6 +1,5 @@ package com.mhss.app.presentation -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -26,13 +25,12 @@ import com.mhss.app.domain.model.Task import com.mhss.app.ui.R import com.mhss.app.ui.theme.LightGray -@OptIn(ExperimentalFoundationApi::class) @Composable fun TasksDashboardWidget( modifier: Modifier = Modifier, tasks: List, onTaskClick: (Task) -> Unit = {}, - onCheck: (Task) -> Unit = {}, + onCheck: (Task, Boolean) -> Unit = {_,_ ->}, onAddClick: () -> Unit = {}, onClick: () -> Unit = {} ) { @@ -92,7 +90,7 @@ fun TasksDashboardWidget( TaskDashboardItem( task = it, onClick = { onTaskClick(it) }, - onComplete = { onCheck(it.copy(isCompleted = !it.isCompleted)) }, + onComplete = { onCheck(it, !it.isCompleted) }, modifier = Modifier.animateItem() ) } diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksViewModel.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksViewModel.kt index 2746a5d9..2ac2fb92 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksViewModel.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksViewModel.kt @@ -17,7 +17,6 @@ import com.mhss.app.preferences.domain.model.toInt import com.mhss.app.preferences.domain.model.toOrder import com.mhss.app.preferences.domain.use_case.GetPreferenceUseCase import com.mhss.app.preferences.domain.use_case.SavePreferenceUseCase -import com.mhss.app.util.date.now import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -27,19 +26,14 @@ import org.koin.android.annotation.KoinViewModel class TasksViewModel( private val addTask: AddTaskUseCase, private val getAllTasks: GetAllTasksUseCase, - private val getTaskUseCase: GetTaskByIdUseCase, - private val updateTask: UpdateTaskUseCase, private val completeTask: UpdateTaskCompletedUseCase, getPreference: GetPreferenceUseCase, private val savePreference: SavePreferenceUseCase, - private val deleteTask: DeleteTaskUseCase, private val searchTasksUseCase: SearchTasksUseCase ) : ViewModel() { var tasksUiState by mutableStateOf(UiState()) private set - var taskDetailsUiState by mutableStateOf(TaskUiState()) - private set private var getTasksJob: Job? = null private var searchTasksJob: Job? = null @@ -84,7 +78,6 @@ class TasksViewModel( TaskEvent.ErrorDisplayed -> { tasksUiState = tasksUiState.copy(error = null, errorAlarm = false) - taskDetailsUiState = taskDetailsUiState.copy(error = null, errorAlarm = false) } is TaskEvent.UpdateOrder -> viewModelScope.launch { @@ -106,37 +99,6 @@ class TasksViewModel( searchTasks(event.query) } } - - is TaskEvent.UpdateTask -> viewModelScope.launch { - if (event.task.title.isBlank()) - taskDetailsUiState = - taskDetailsUiState.copy(error = R.string.error_empty_title) - else { - val scheduleAlarmSuccess = updateTask( - event.task.copy(updatedDate = now()), - taskDetailsUiState.task - ) - taskDetailsUiState = if (scheduleAlarmSuccess) { - taskDetailsUiState.copy(navigateUp = true) - } else { - taskDetailsUiState.copy( - error = R.string.no_alarm_permission, - errorAlarm = true - ) - } - } - } - - is TaskEvent.DeleteTask -> viewModelScope.launch { - deleteTask(event.task) - taskDetailsUiState = taskDetailsUiState.copy(navigateUp = true) - } - - is TaskEvent.GetTask -> viewModelScope.launch { - taskDetailsUiState = taskDetailsUiState.copy( - task = getTaskUseCase(event.taskId) - ) - } } } @@ -149,13 +111,6 @@ class TasksViewModel( val searchTasks: List = emptyList() ) - data class TaskUiState( - val task: Task = Task(""), - val navigateUp: Boolean = false, - val error: Int? = null, - val errorAlarm: Boolean = false - ) - private fun getTasks(order: Order, showCompleted: Boolean) { getTasksJob?.cancel() getTasksJob = getAllTasks(order) From 79abd151fef70e459ae0090645d3997062ad2dff Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 2 Oct 2024 20:36:17 +0300 Subject: [PATCH 12/31] Update Dashboard widgets list background color --- .../app/presentation/CalendarDashboardWidget.kt | 12 ++++++------ .../{TaskDashboardItem.kt => TaskSmallCard.kt} | 6 +++--- .../mhss/app/presentation/TasksDashboardWidget.kt | 15 +++++++-------- 3 files changed, 16 insertions(+), 17 deletions(-) rename tasks/presentation/src/main/java/com/mhss/app/presentation/{TaskDashboardItem.kt => TaskSmallCard.kt} (98%) diff --git a/calendar/presentation/src/main/java/com/mhss/app/presentation/CalendarDashboardWidget.kt b/calendar/presentation/src/main/java/com/mhss/app/presentation/CalendarDashboardWidget.kt index d327e733..ee7c6c00 100644 --- a/calendar/presentation/src/main/java/com/mhss/app/presentation/CalendarDashboardWidget.kt +++ b/calendar/presentation/src/main/java/com/mhss/app/presentation/CalendarDashboardWidget.kt @@ -11,15 +11,13 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.mhss.app.ui.R import com.mhss.app.domain.model.CalendarEvent import com.mhss.app.util.permissions.Permission -import com.mhss.app.ui.theme.LightGray import com.mhss.app.util.permissions.rememberPermissionState @Composable @@ -43,8 +41,6 @@ fun CalendarDashboardWidget( val readCalendarPermissionState = rememberPermissionState( Permission.READ_CALENDAR ) - // Workaround replacement to Material2 `isLight` - val isDark = MaterialTheme.colorScheme.background.luminance() <= 0.5 Column( modifier = modifier .clickable { onClick() } @@ -73,7 +69,11 @@ fun CalendarDashboardWidget( modifier = Modifier .fillMaxSize() .clip(RoundedCornerShape(20.dp)) - .background(if (isDark) Color.DarkGray else LightGray), + .background( + MaterialTheme.colorScheme.onSurfaceVariant.copy(0.1f).compositeOver( + MaterialTheme.colorScheme.surfaceVariant + ) + ), contentPadding = PaddingValues(vertical = 10.dp, horizontal = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDashboardItem.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskSmallCard.kt similarity index 98% rename from tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDashboardItem.kt rename to tasks/presentation/src/main/java/com/mhss/app/presentation/TaskSmallCard.kt index 40b04c33..cd84188e 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskDashboardItem.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TaskSmallCard.kt @@ -31,7 +31,7 @@ import com.mhss.app.util.date.formatDateDependingOnDay import com.mhss.app.util.date.isDueDateOverdue @Composable -fun TaskDashboardItem( +fun TaskSmallCard( modifier: Modifier = Modifier, task: Task, onComplete: () -> Unit, @@ -115,8 +115,8 @@ fun TaskDashboardCheckBox( @Preview @Composable -fun TaskDashboardItemPreview() { - TaskDashboardItem( +fun TaskSmallCardPreview() { + TaskSmallCard( task = Task( title = "Task 1", description = "Task 1 description", diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt index 3d826f58..583ba8d2 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksDashboardWidget.kt @@ -15,15 +15,13 @@ 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.graphics.Color -import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.mhss.app.domain.model.Task import com.mhss.app.ui.R -import com.mhss.app.ui.theme.LightGray @Composable fun TasksDashboardWidget( @@ -43,8 +41,6 @@ fun TasksDashboardWidget( .fillMaxWidth() .padding(8.dp) ) { - // Workaround replacement to Material2 `isLight` - val isDark = MaterialTheme.colorScheme.background.luminance() <= 0.5 Column( modifier = modifier .clickable { onClick() } @@ -73,7 +69,10 @@ fun TasksDashboardWidget( modifier = Modifier .fillMaxSize() .clip(RoundedCornerShape(20.dp)) - .background(if (isDark) Color.DarkGray else LightGray), + .background( + MaterialTheme.colorScheme.onSurfaceVariant.copy(0.1f).compositeOver( + MaterialTheme.colorScheme.surfaceVariant) + ), contentPadding = PaddingValues(vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { @@ -86,8 +85,8 @@ fun TasksDashboardWidget( textAlign = TextAlign.Center ) } - } else items(tasks) { - TaskDashboardItem( + } else items(tasks, key = { it.id }) { + TaskSmallCard( task = it, onClick = { onTaskClick(it) }, onComplete = { onCheck(it, !it.isCompleted) }, From 5ad027f51b8816d24ac0e9ad94617cc7c67dcd50 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 2 Oct 2024 20:37:40 +0300 Subject: [PATCH 13/31] Set isDebuggable = true in debug type --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 359af8e0..b3213875 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,7 +34,7 @@ android { debug { isMinifyEnabled = false applicationIdSuffix = ".debug" - isDebuggable = false + isDebuggable = true resValue("string", "app_name", "MyBrain Debug") } } From e56b0d032ba8bdea17b93845c1e3299f5c170e5f Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 2 Oct 2024 21:50:20 +0300 Subject: [PATCH 14/31] Replace BackHandler auto save with onStopOrDispose in BookmarkDetailsScreen --- .../mhss/app/data/BookmarkRepositoryImpl.kt | 4 +- .../domain/repository/BookmarkRepository.kt | 2 +- .../app/presentation/BookmarkDetailsEvent.kt | 9 ++ .../app/presentation/BookmarkDetailsScreen.kt | 123 +++++------------- .../presentation/BookmarkDetailsViewModel.kt | 90 +++++++++++++ .../mhss/app/presentation/BookmarkEvent.kt | 5 +- .../app/presentation/BookmarksViewModel.kt | 44 +------ .../com/mhss/app/database/dao/BookmarkDao.kt | 2 +- .../java/com/mhss/app/database/dao/NoteDao.kt | 2 +- .../com/mhss/app/data/NoteRepositoryImpl.kt | 4 +- .../app/domain/repository/NoteRepository.kt | 2 +- .../app/presentation/NoteDetailsViewModel.kt | 10 +- 12 files changed, 150 insertions(+), 147 deletions(-) create mode 100644 bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsEvent.kt create mode 100644 bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsViewModel.kt diff --git a/bookmarks/data/src/main/java/com/mhss/app/data/BookmarkRepositoryImpl.kt b/bookmarks/data/src/main/java/com/mhss/app/data/BookmarkRepositoryImpl.kt index d89fe052..f58cf619 100644 --- a/bookmarks/data/src/main/java/com/mhss/app/data/BookmarkRepositoryImpl.kt +++ b/bookmarks/data/src/main/java/com/mhss/app/data/BookmarkRepositoryImpl.kt @@ -41,8 +41,8 @@ class BookmarkRepositoryImpl( } } - override suspend fun addBookmark(bookmark: Bookmark) { - withContext(ioDispatcher) { + override suspend fun addBookmark(bookmark: Bookmark): Long { + return withContext(ioDispatcher) { bookmarkDao.insertBookmark(bookmark.toBookmarkEntity()) } } diff --git a/bookmarks/domain/src/main/java/com/mhss/app/domain/repository/BookmarkRepository.kt b/bookmarks/domain/src/main/java/com/mhss/app/domain/repository/BookmarkRepository.kt index 52d91661..8a2feaa9 100644 --- a/bookmarks/domain/src/main/java/com/mhss/app/domain/repository/BookmarkRepository.kt +++ b/bookmarks/domain/src/main/java/com/mhss/app/domain/repository/BookmarkRepository.kt @@ -11,7 +11,7 @@ interface BookmarkRepository { suspend fun searchBookmarks(query: String): List - suspend fun addBookmark(bookmark: Bookmark) + suspend fun addBookmark(bookmark: Bookmark): Long suspend fun deleteBookmark(bookmark: Bookmark) diff --git a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsEvent.kt b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsEvent.kt new file mode 100644 index 00000000..6b503f21 --- /dev/null +++ b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsEvent.kt @@ -0,0 +1,9 @@ +package com.mhss.app.presentation + +import com.mhss.app.domain.model.Bookmark + +sealed class BookmarkDetailsEvent { + data class ScreenOnStop(val bookmark: Bookmark): BookmarkDetailsEvent() + data class DeleteBookmark(val bookmark: Bookmark) : BookmarkDetailsEvent() + data object ErrorDisplayed : BookmarkDetailsEvent() +} \ No newline at end of file diff --git a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsScreen.kt b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsScreen.kt index 5880ebe5..f732b4ad 100644 --- a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsScreen.kt +++ b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsScreen.kt @@ -1,12 +1,10 @@ package com.mhss.app.presentation -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -14,28 +12,27 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.navigation.NavHostController import com.mhss.app.ui.R import com.mhss.app.domain.model.Bookmark import com.mhss.app.ui.components.common.MyBrainAppBar +import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @Composable fun BookmarkDetailsScreen( navController: NavHostController, bookmarkId: Int, - viewModel: BookmarksViewModel = koinViewModel() + viewModel: BookmarkDetailsViewModel = koinViewModel(parameters = { parametersOf(bookmarkId) }), ) { - LaunchedEffect(true) { - if (bookmarkId != -1) { - viewModel.onEvent(BookmarkEvent.GetBookmark(bookmarkId)) - } - } val context = LocalContext.current val uriHandler = LocalUriHandler.current - val state = viewModel.uiState + val state = viewModel.bookmarkDetailsUiState val snackbarHostState = remember { SnackbarHostState() } var openDialog by rememberSaveable { mutableStateOf(false) } + val scope = rememberCoroutineScope() var title by rememberSaveable { mutableStateOf(state.bookmark?.title ?: "") } var description by rememberSaveable { mutableStateOf(state.bookmark?.description ?: "") } @@ -57,45 +54,21 @@ fun BookmarkDetailsScreen( snackbarHostState.showSnackbar( context.getString(state.error) ) - viewModel.onEvent(BookmarkEvent.ErrorDisplayed) + viewModel.onEvent(BookmarkDetailsEvent.ErrorDisplayed) } } - BackHandler { - addOrUpdateBookmark( - Bookmark( - title = title, - description = description, - url = url - ), - state.bookmark, - onNotChanged = { - navController.navigateUp() - }, - onUpdate = { - if (state.bookmark != null) { - viewModel.onEvent( - BookmarkEvent.UpdateBookmark( - state.bookmark.copy( - title = title, - description = description, - url = url - ) - ) - ) - } else { - viewModel.onEvent( - BookmarkEvent.AddBookmark( - Bookmark( - title = title, - description = description, - url = url - ) - ) + LifecycleStartEffect(Unit) { + onStopOrDispose { + viewModel.onEvent( + BookmarkDetailsEvent.ScreenOnStop( + Bookmark( + title = title, + description = description, + url = url ) - } - - } - ) + ) + ) + } } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, @@ -109,16 +82,21 @@ fun BookmarkDetailsScreen( contentDescription = stringResource(R.string.delete_bookmark) ) } - if (url.isValidUrl()) - IconButton(onClick = { + IconButton(onClick = { + if (url.isValidUrl()) { uriHandler.openUri(if (!url.startsWith("https://") && !url.startsWith("http://")) "http://$url" else url) - }) { - Icon( - painter = painterResource(id = R.drawable.ic_open_link), - contentDescription = stringResource(R.string.open_link), - modifier = Modifier.size(24.dp), + } else scope.launch { + snackbarHostState.showSnackbar( + context.getString(R.string.invalid_url) ) } + }) { + Icon( + painter = painterResource(id = R.drawable.ic_open_link), + contentDescription = stringResource(R.string.open_link), + modifier = Modifier.size(24.dp), + ) + } } ) }, @@ -152,20 +130,6 @@ fun BookmarkDetailsScreen( shape = RoundedCornerShape(15.dp), modifier = Modifier.fillMaxWidth(), ) - Spacer(Modifier.height(8.dp)) - TextButton( - onClick = { - if (state.bookmark != null) - url = state.bookmark.url - else navController.navigateUp() - }, - modifier = Modifier.align(Alignment.End) - ) { - Text( - text = if (state.bookmark != null) stringResource(R.string.cancel_changes) - else stringResource(R.string.cancel) - ) - } } if (openDialog) AlertDialog( @@ -184,7 +148,7 @@ fun BookmarkDetailsScreen( colors = ButtonDefaults.buttonColors(containerColor = Color.Red), shape = RoundedCornerShape(25.dp), onClick = { - viewModel.onEvent(BookmarkEvent.DeleteBookmark(state.bookmark!!)) + viewModel.onEvent(BookmarkDetailsEvent.DeleteBookmark(state.bookmark!!)) }, ) { Text(stringResource(R.string.delete_bookmark), color = Color.White) @@ -201,29 +165,4 @@ fun BookmarkDetailsScreen( } ) } -} - -private fun addOrUpdateBookmark( - newBookmark: Bookmark, - bookmark: Bookmark? = null, - onNotChanged: () -> Unit = {}, - onUpdate: (Bookmark) -> Unit, -) { - if (bookmark != null) { - if (bookmarkChanged(newBookmark, bookmark)) - onUpdate(bookmark) - else - onNotChanged() - } else { - onUpdate(newBookmark) - } -} - -private fun bookmarkChanged( - bookmark: Bookmark, - newBookmark: Bookmark -): Boolean { - return bookmark.title != newBookmark.title || - bookmark.description != newBookmark.description || - bookmark.url != newBookmark.url } \ No newline at end of file diff --git a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsViewModel.kt b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsViewModel.kt new file mode 100644 index 00000000..827c4fd6 --- /dev/null +++ b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkDetailsViewModel.kt @@ -0,0 +1,90 @@ +package com.mhss.app.presentation + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mhss.app.domain.model.Bookmark +import com.mhss.app.domain.use_case.AddBookmarkUseCase +import com.mhss.app.domain.use_case.DeleteBookmarkUseCase +import com.mhss.app.domain.use_case.GetBookmarkUseCase +import com.mhss.app.domain.use_case.UpdateBookmarkUseCase +import com.mhss.app.util.date.now +import kotlinx.coroutines.launch +import org.koin.android.annotation.KoinViewModel + +@KoinViewModel +class BookmarkDetailsViewModel( + private val getBookmark: GetBookmarkUseCase, + private val updateBookmark: UpdateBookmarkUseCase, + private val addBookmark: AddBookmarkUseCase, + private val deleteBookmark: DeleteBookmarkUseCase, + bookmarkId: Int, +) : ViewModel() { + + var bookmarkDetailsUiState by mutableStateOf(BookmarkDetailsUiState()) + private set + + init { + viewModelScope.launch { + val bookmark = if (bookmarkId != -1) getBookmark(bookmarkId) else null + bookmarkDetailsUiState = bookmarkDetailsUiState.copy( + bookmark = bookmark + ) + } + } + + fun onEvent(event: BookmarkDetailsEvent) { + when (event) { + BookmarkDetailsEvent.ErrorDisplayed -> { + bookmarkDetailsUiState = bookmarkDetailsUiState.copy(error = null) + } + + is BookmarkDetailsEvent.ScreenOnStop -> viewModelScope.launch { + if (bookmarkDetailsUiState.bookmark == null) { + if (event.bookmark.url.isNotBlank() + || event.bookmark.title.isNotBlank() + || event.bookmark.description.isNotBlank() + ) { + val bookmark = event.bookmark.copy( + createdDate = now(), + updatedDate = now() + ) + val id = addBookmark(bookmark) + bookmarkDetailsUiState = bookmarkDetailsUiState.copy(bookmark = bookmark.copy(id = id.toInt())) + } + } else if (bookmarkChanged(bookmarkDetailsUiState.bookmark!!, event.bookmark)) { + val newBookmark = bookmarkDetailsUiState.bookmark!!.copy( + title = event.bookmark.title, + description = event.bookmark.description, + url = event.bookmark.url, + updatedDate = now() + ) + updateBookmark(newBookmark) + bookmarkDetailsUiState = bookmarkDetailsUiState.copy(bookmark = newBookmark) + } + } + + is BookmarkDetailsEvent.DeleteBookmark -> viewModelScope.launch { + deleteBookmark(bookmarkDetailsUiState.bookmark!!) + bookmarkDetailsUiState = bookmarkDetailsUiState.copy(navigateUp = true) + } + } + } + + data class BookmarkDetailsUiState( + val bookmark: Bookmark? = null, + val navigateUp: Boolean = false, + val error: Int? = null, + ) + + private fun bookmarkChanged( + bookmark: Bookmark, + newBookmark: Bookmark, + ): Boolean { + return bookmark.title != newBookmark.title || + bookmark.description != newBookmark.description || + bookmark.url != newBookmark.url + } +} \ No newline at end of file diff --git a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkEvent.kt b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkEvent.kt index dc598131..50374f6c 100644 --- a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkEvent.kt +++ b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarkEvent.kt @@ -6,11 +6,8 @@ import com.mhss.app.ui.ItemView sealed class BookmarkEvent { data class AddBookmark(val bookmark: Bookmark) : BookmarkEvent() - data class GetBookmark(val bookmarkId: Int) : BookmarkEvent() data class SearchBookmarks(val query: String) : BookmarkEvent() data class UpdateOrder(val order: Order) : BookmarkEvent() data class UpdateView(val view: ItemView) : BookmarkEvent() - data class UpdateBookmark(val bookmark: Bookmark) : BookmarkEvent() - data class DeleteBookmark(val bookmark: Bookmark) : BookmarkEvent() - object ErrorDisplayed: BookmarkEvent() + data object ErrorDisplayed: BookmarkEvent() } diff --git a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarksViewModel.kt b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarksViewModel.kt index fb747eb6..dbd98c3c 100644 --- a/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarksViewModel.kt +++ b/bookmarks/presentation/src/main/java/com/mhss/app/presentation/BookmarksViewModel.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.mhss.app.preferences.PrefsConstants -import com.mhss.app.ui.R import com.mhss.app.domain.model.Bookmark import com.mhss.app.domain.use_case.* import com.mhss.app.preferences.domain.model.Order @@ -18,7 +17,6 @@ import com.mhss.app.preferences.domain.use_case.GetPreferenceUseCase import com.mhss.app.preferences.domain.use_case.SavePreferenceUseCase import com.mhss.app.ui.ItemView import com.mhss.app.ui.toNotesView -import com.mhss.app.util.date.now import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine @@ -30,16 +28,12 @@ import org.koin.android.annotation.KoinViewModel @KoinViewModel class BookmarksViewModel( private val addBookmark: AddBookmarkUseCase, - private val updateBookmark: UpdateBookmarkUseCase, - private val deleteBookmark: DeleteBookmarkUseCase, private val getAlBookmarks: GetAllBookmarksUseCase, private val searchBookmarks: SearchBookmarksUseCase, - private val getBookmark: GetBookmarkUseCase, getPreference: GetPreferenceUseCase, - private val savePreference: SavePreferenceUseCase + private val savePreference: SavePreferenceUseCase, ) : ViewModel() { - var uiState by mutableStateOf(UiState()) private set @@ -69,52 +63,28 @@ class BookmarksViewModel( fun onEvent(event: BookmarkEvent) { when (event) { is BookmarkEvent.AddBookmark -> viewModelScope.launch { - uiState = if ( - event.bookmark.url.isBlank() - && event.bookmark.title.isBlank() - && event.bookmark.description.isBlank() - ) - uiState.copy(navigateUp = true) - else { - if (event.bookmark.url.isValidUrl()) { - addBookmark(event.bookmark) - uiState.copy(navigateUp = true) - } else - uiState.copy(error = R.string.invalid_url) - } - } - is BookmarkEvent.DeleteBookmark -> viewModelScope.launch { - deleteBookmark(event.bookmark) - uiState = uiState.copy(navigateUp = true) - } - is BookmarkEvent.GetBookmark -> viewModelScope.launch { - val bookmark = getBookmark(event.bookmarkId) - uiState = uiState.copy(bookmark = bookmark) + addBookmark(event.bookmark) } + is BookmarkEvent.SearchBookmarks -> viewModelScope.launch { val bookmarks = searchBookmarks(event.query) uiState = uiState.copy(searchBookmarks = bookmarks) } - is BookmarkEvent.UpdateBookmark -> viewModelScope.launch { - uiState = if (!event.bookmark.url.isValidUrl()) { - uiState.copy(error = R.string.invalid_url) - } else { - updateBookmark(event.bookmark.copy(updatedDate = now())) - uiState.copy(navigateUp = true) - } - } + is BookmarkEvent.UpdateOrder -> viewModelScope.launch { savePreference( intPreferencesKey(PrefsConstants.BOOKMARK_ORDER_KEY), event.order.toInt() ) } + is BookmarkEvent.UpdateView -> viewModelScope.launch { savePreference( intPreferencesKey(PrefsConstants.BOOKMARK_VIEW_KEY), event.view.value ) } + BookmarkEvent.ErrorDisplayed -> uiState = uiState.copy(error = null) } } @@ -123,10 +93,8 @@ class BookmarksViewModel( val bookmarks: List = emptyList(), val bookmarksOrder: Order = Order.DateModified(OrderType.ASC), val bookmarksView: ItemView = ItemView.LIST, - val bookmark: Bookmark? = null, val error: Int? = null, val searchBookmarks: List = emptyList(), - val navigateUp: Boolean = false ) private fun getBookmarks(order: Order) { diff --git a/core/database/src/main/java/com/mhss/app/database/dao/BookmarkDao.kt b/core/database/src/main/java/com/mhss/app/database/dao/BookmarkDao.kt index d10a2f9a..d41d397a 100644 --- a/core/database/src/main/java/com/mhss/app/database/dao/BookmarkDao.kt +++ b/core/database/src/main/java/com/mhss/app/database/dao/BookmarkDao.kt @@ -17,7 +17,7 @@ interface BookmarkDao { suspend fun getBookmark(query: String): List @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertBookmark(bookmark: BookmarkEntity) + suspend fun insertBookmark(bookmark: BookmarkEntity): Long @Update suspend fun updateBookmark(bookmark: BookmarkEntity) diff --git a/core/database/src/main/java/com/mhss/app/database/dao/NoteDao.kt b/core/database/src/main/java/com/mhss/app/database/dao/NoteDao.kt index 602a4622..d83735c9 100644 --- a/core/database/src/main/java/com/mhss/app/database/dao/NoteDao.kt +++ b/core/database/src/main/java/com/mhss/app/database/dao/NoteDao.kt @@ -24,7 +24,7 @@ interface NoteDao { fun getNotesByFolder(folderId: Int): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertNote(note: NoteEntity) + suspend fun insertNote(note: NoteEntity): Long @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertNotes(notes: List) diff --git a/notes/data/src/main/java/com/mhss/app/data/NoteRepositoryImpl.kt b/notes/data/src/main/java/com/mhss/app/data/NoteRepositoryImpl.kt index f69e158a..789578f3 100644 --- a/notes/data/src/main/java/com/mhss/app/data/NoteRepositoryImpl.kt +++ b/notes/data/src/main/java/com/mhss/app/data/NoteRepositoryImpl.kt @@ -54,8 +54,8 @@ class NoteRepositoryImpl( } } - override suspend fun addNote(note: Note) { - withContext(ioDispatcher) { + override suspend fun addNote(note: Note): Long { + return withContext(ioDispatcher) { noteDao.insertNote(note.toNoteEntity()) } } diff --git a/notes/domain/src/main/java/com/mhss/app/domain/repository/NoteRepository.kt b/notes/domain/src/main/java/com/mhss/app/domain/repository/NoteRepository.kt index 433073ec..26470303 100644 --- a/notes/domain/src/main/java/com/mhss/app/domain/repository/NoteRepository.kt +++ b/notes/domain/src/main/java/com/mhss/app/domain/repository/NoteRepository.kt @@ -14,7 +14,7 @@ interface NoteRepository { fun getNotesByFolder(folderId: Int): Flow> - suspend fun addNote(note: Note) + suspend fun addNote(note: Note): Long suspend fun updateNote(note: Note) diff --git a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt index be9f1816..b2a6e9ae 100644 --- a/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt +++ b/notes/presentation/src/main/java/com/mhss/app/presentation/NoteDetailsViewModel.kt @@ -123,12 +123,12 @@ class NoteDetailsViewModel( is NoteDetailsEvent.ScreenOnStop -> viewModelScope.launch { if (noteUiState.note == null) { if (event.currentNote.title.isNotBlank() || event.currentNote.content.isNotBlank()) { - addNote( - event.currentNote.copy( - createdDate = now(), - updatedDate = now() - ) + val note = event.currentNote.copy( + createdDate = now(), + updatedDate = now() ) + val id = addNote(note) + noteUiState = noteUiState.copy(note = note.copy(id = id.toInt())) } } else if (noteChanged(noteUiState.note!!, event.currentNote)) { val newNote = noteUiState.note!!.copy( From 39462e1d93de95c717a457ca0d5cfa419ae6c385 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Thu, 3 Oct 2024 17:46:58 +0300 Subject: [PATCH 15/31] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 59d2e3ae..0d224346 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ local.properties /.idea/appInsightsSettings.xml /.kotlin/ /.idea/studiobot.xml +/.idea/runConfigurations.xml +/.idea/gradle.xml +/.idea/compiler.xml From 56a6ab499f3792d6fd53e4bbd440699c563e5e53 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Thu, 3 Oct 2024 17:55:42 +0300 Subject: [PATCH 16/31] Replace BackHandler auto save with onStopOrDispose in DiaryEntryDetailsScreen --- .idea/compiler.xml | 6 -- .idea/gradle.xml | 58 ----------- .../com/mhss/app/database/dao/DiaryDao.kt | 2 +- .../com/mhss/app/data/DiaryRepositoryImpl.kt | 4 +- .../app/domain/repository/DiaryRepository.kt | 2 +- .../app/presentation/DiaryDetailsEvent.kt | 9 ++ .../app/presentation/DiaryDetailsViewModel.kt | 81 +++++++++++++++ .../presentation/DiaryEntryDetailsScreen.kt | 99 +++++-------------- .../com/mhss/app/presentation/DiaryEvent.kt | 7 -- .../mhss/app/presentation/DiaryViewModel.kt | 37 +------ 10 files changed, 118 insertions(+), 187 deletions(-) delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/gradle.xml create mode 100644 diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsEvent.kt create mode 100644 diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsViewModel.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56e..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index c68ae2bd..00000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/core/database/src/main/java/com/mhss/app/database/dao/DiaryDao.kt b/core/database/src/main/java/com/mhss/app/database/dao/DiaryDao.kt index 8e09cf60..911f4548 100644 --- a/core/database/src/main/java/com/mhss/app/database/dao/DiaryDao.kt +++ b/core/database/src/main/java/com/mhss/app/database/dao/DiaryDao.kt @@ -17,7 +17,7 @@ interface DiaryDao { suspend fun getEntriesByTitle(query: String): List @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertEntry(diary: DiaryEntryEntity) + suspend fun insertEntry(diary: DiaryEntryEntity): Long @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertEntries(diary: List) diff --git a/diary/data/src/main/java/com/mhss/app/data/DiaryRepositoryImpl.kt b/diary/data/src/main/java/com/mhss/app/data/DiaryRepositoryImpl.kt index 3ed53272..ba818c7d 100644 --- a/diary/data/src/main/java/com/mhss/app/data/DiaryRepositoryImpl.kt +++ b/diary/data/src/main/java/com/mhss/app/data/DiaryRepositoryImpl.kt @@ -38,8 +38,8 @@ class DiaryRepositoryImpl( } } - override suspend fun addEntry(diary: DiaryEntry) { - withContext(ioDispatcher) { + override suspend fun addEntry(diary: DiaryEntry): Long { + return withContext(ioDispatcher) { diaryDao.insertEntry(diary.toDiaryEntryEntity()) } } diff --git a/diary/domain/src/main/java/com/mhss/app/domain/repository/DiaryRepository.kt b/diary/domain/src/main/java/com/mhss/app/domain/repository/DiaryRepository.kt index 89703e74..c68232d4 100644 --- a/diary/domain/src/main/java/com/mhss/app/domain/repository/DiaryRepository.kt +++ b/diary/domain/src/main/java/com/mhss/app/domain/repository/DiaryRepository.kt @@ -11,7 +11,7 @@ interface DiaryRepository { suspend fun searchEntries(title: String): List - suspend fun addEntry(diary: DiaryEntry) + suspend fun addEntry(diary: DiaryEntry): Long suspend fun updateEntry(diary: DiaryEntry) diff --git a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsEvent.kt b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsEvent.kt new file mode 100644 index 00000000..f80cd3fd --- /dev/null +++ b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsEvent.kt @@ -0,0 +1,9 @@ +package com.mhss.app.presentation + +import com.mhss.app.domain.model.DiaryEntry + +sealed class DiaryDetailsEvent { + data object DeleteEntry : DiaryDetailsEvent() + data object ToggleReadingMode : DiaryDetailsEvent() + data class ScreenOnStop(val currentEntry: DiaryEntry) : DiaryDetailsEvent() +} \ No newline at end of file diff --git a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsViewModel.kt b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsViewModel.kt new file mode 100644 index 00000000..b7c11e19 --- /dev/null +++ b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryDetailsViewModel.kt @@ -0,0 +1,81 @@ +package com.mhss.app.presentation + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mhss.app.domain.model.DiaryEntry +import com.mhss.app.domain.use_case.* +import com.mhss.app.util.date.now +import kotlinx.coroutines.launch +import org.koin.android.annotation.KoinViewModel + +@KoinViewModel +class DiaryDetailsViewModel( + private val getEntry: GetDiaryEntryUseCase, + private val addEntry: AddDiaryEntryUseCase, + private val updateEntry: UpdateDiaryEntryUseCase, + private val deleteEntry: DeleteDiaryEntryUseCase, + entryId: Int +) : ViewModel() { + + var uiState by mutableStateOf(UiState()) + private set + + init { + viewModelScope.launch { + if (entryId != -1) { + uiState = uiState.copy( + entry = getEntry(entryId), + readingMode = true + ) + } + } + } + + fun onEvent(event: DiaryDetailsEvent) { + when (event) { + is DiaryDetailsEvent.DeleteEntry -> viewModelScope.launch { + deleteEntry(uiState.entry!!) + uiState = uiState.copy(navigateUp = true) + } + is DiaryDetailsEvent.ToggleReadingMode -> { + uiState = uiState.copy(readingMode = !uiState.readingMode) + } + is DiaryDetailsEvent.ScreenOnStop -> viewModelScope.launch { + if (uiState.entry == null) { + if (event.currentEntry.title.isNotBlank() || event.currentEntry.content.isNotBlank()) { + val entry = event.currentEntry.copy( + updatedDate = now() + ) + val id = addEntry(entry) + uiState = uiState.copy(entry = entry.copy(id = id.toInt())) + } + } else if (entryChanged(uiState.entry!!, event.currentEntry)) { + val newEntry = uiState.entry!!.copy( + title = event.currentEntry.title, + content = event.currentEntry.content, + mood = event.currentEntry.mood, + createdDate = event.currentEntry.createdDate, + updatedDate = now() + ) + updateEntry(newEntry) + uiState = uiState.copy(entry = newEntry) + } + } + } + } + + private fun entryChanged(entry: DiaryEntry, newEntry: DiaryEntry): Boolean { + return entry.title != newEntry.title || + entry.content != newEntry.content || + entry.mood != newEntry.mood + } + + data class UiState( + val entry: DiaryEntry? = null, + val navigateUp: Boolean = false, + val readingMode: Boolean = false + ) +} \ No newline at end of file diff --git a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt index 455b8e23..341b2788 100644 --- a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt +++ b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt @@ -1,6 +1,5 @@ package com.mhss.app.presentation -import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* @@ -19,6 +18,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.navigation.NavHostController import com.mhss.app.domain.model.DiaryEntry import com.mhss.app.domain.model.Mood @@ -29,31 +29,22 @@ import com.mhss.app.util.date.fullDate import com.mhss.app.util.date.now import dev.jeziellago.compose.markdowntext.MarkdownText import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf -@OptIn(ExperimentalMaterial3Api::class) @Composable fun DiaryEntryDetailsScreen( navController: NavHostController, entryId: Int, - viewModel: DiaryViewModel = koinViewModel() + viewModel: DiaryDetailsViewModel = koinViewModel(parameters = { parametersOf(entryId) }) ) { - LaunchedEffect(true) { - if (entryId != -1) { - viewModel.onEvent(DiaryEvent.GetEntry(entryId)) - } - } val state = viewModel.uiState val snackbarHostState = remember { SnackbarHostState() } var openDialog by rememberSaveable { mutableStateOf(false) } - var title by rememberSaveable { mutableStateOf(state.entry?.title ?: "") } - var content by rememberSaveable { mutableStateOf(state.entry?.content ?: "") } - var mood by rememberSaveable { mutableStateOf(state.entry?.mood ?: Mood.OKAY) } - var date by rememberSaveable { - mutableLongStateOf( - state.entry?.createdDate ?: now() - ) - } + var title by remember { mutableStateOf("") } + var content by remember { mutableStateOf("") } + var mood by remember { mutableStateOf(Mood.OKAY) } + var date by remember { mutableLongStateOf(now()) } val readingMode = state.readingMode var showDateDialog by remember { mutableStateOf(false) @@ -61,42 +52,32 @@ fun DiaryEntryDetailsScreen( val context = LocalContext.current LaunchedEffect(state.entry) { - if (state.entry != null && title.isBlank() && content.isBlank()) { + if (state.entry != null) { title = state.entry.title content = state.entry.content date = state.entry.createdDate mood = state.entry.mood } } - LaunchedEffect(state) { + LaunchedEffect(state.navigateUp) { if (state.navigateUp) { openDialog = false navController.navigateUp() } - if (state.error != null) { - snackbarHostState.showSnackbar( - state.error - ) - viewModel.onEvent(DiaryEvent.ErrorDisplayed) - } } - BackHandler { - if (state.entry != null) { - val entry = state.entry.copy( - title = title, - content = content, - mood = mood, - createdDate = date, - updatedDate = now() - ) - if (entryChanged( - state.entry, - entry + LifecycleStartEffect(Unit) { + onStopOrDispose { + viewModel.onEvent( + DiaryDetailsEvent.ScreenOnStop( + DiaryEntry( + title = title, + content = content, + mood = mood, + createdDate = date + ) ) - ) viewModel.onEvent(DiaryEvent.UpdateEntry(entry)) - else navController.navigateUp() - } else - navController.navigateUp() + ) + } } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, @@ -105,7 +86,7 @@ fun DiaryEntryDetailsScreen( title = "", actions = { IconButton(onClick = { - viewModel.onEvent(DiaryEvent.ToggleReadingMode) + viewModel.onEvent(DiaryDetailsEvent.ToggleReadingMode) }) { Icon( painter = painterResource(id = R.drawable.ic_read_mode), @@ -131,30 +112,6 @@ fun DiaryEntryDetailsScreen( } } ) - }, - floatingActionButton = { - if (state.entry == null) - FloatingActionButton( - onClick = { - val entry = DiaryEntry( - title = title, - content = content, - mood = mood, - createdDate = date, - updatedDate = now() - ) - viewModel.onEvent(DiaryEvent.AddEntry(entry)) - - }, - containerColor = MaterialTheme.colorScheme.primary, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_save), - contentDescription = stringResource(R.string.save_entry), - modifier = Modifier.size(25.dp), - tint = Color.White - ) - } } ) { paddingValues -> Column( @@ -231,7 +188,7 @@ fun DiaryEntryDetailsScreen( colors = ButtonDefaults.buttonColors(containerColor = Color.Red), shape = RoundedCornerShape(25.dp), onClick = { - viewModel.onEvent(DiaryEvent.DeleteEntry(state.entry!!)) + viewModel.onEvent(DiaryDetailsEvent.DeleteEntry) }, ) { Text( @@ -302,14 +259,4 @@ private fun MoodItem(mood: Mood, chosen: Boolean, onMoodChange: () -> Unit) { ) } } -} - -private fun entryChanged( - entry: DiaryEntry?, - newEntry: DiaryEntry -): Boolean { - return entry?.title != newEntry.title || - entry.content != newEntry.content || - entry.mood != newEntry.mood || - entry.createdDate != newEntry.createdDate } \ No newline at end of file diff --git a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEvent.kt b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEvent.kt index 79745cf1..fdefca9e 100644 --- a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEvent.kt +++ b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEvent.kt @@ -1,16 +1,9 @@ package com.mhss.app.presentation -import com.mhss.app.domain.model.DiaryEntry import com.mhss.app.preferences.domain.model.Order sealed class DiaryEvent { - data class AddEntry(val entry: DiaryEntry) : DiaryEvent() - data class GetEntry(val entryId: Int) : DiaryEvent() data class SearchEntries(val query: String) : DiaryEvent() data class UpdateOrder(val order: Order) : DiaryEvent() - data class UpdateEntry(val entry: DiaryEntry) : DiaryEvent() - data class DeleteEntry(val entry: DiaryEntry) : DiaryEvent() data class ChangeChartEntriesRange(val monthly: Boolean) : DiaryEvent() - data object ErrorDisplayed: DiaryEvent() - data object ToggleReadingMode: DiaryEvent() } \ No newline at end of file diff --git a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryViewModel.kt b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryViewModel.kt index 6e4f41db..e0af57f1 100644 --- a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryViewModel.kt +++ b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryViewModel.kt @@ -29,12 +29,8 @@ import org.koin.core.annotation.Named @KoinViewModel class DiaryViewModel( - private val addEntry: AddDiaryEntryUseCase, - private val updateEntry: UpdateDiaryEntryUseCase, - private val deleteEntry: DeleteDiaryEntryUseCase, private val getAlEntries: GetAllEntriesUseCase, private val searchEntries: SearchEntriesUseCase, - private val getEntry: GetDiaryEntryUseCase, private val getPreference: GetPreferenceUseCase, private val savePreference: SavePreferenceUseCase, private val getEntriesForChart: GetDiaryForChartUseCase, @@ -59,63 +55,32 @@ class DiaryViewModel( fun onEvent(event: DiaryEvent) { when (event) { - is DiaryEvent.AddEntry -> viewModelScope.launch { - addEntry(event.entry) - uiState = uiState.copy( - navigateUp = true - ) - } - is DiaryEvent.DeleteEntry -> viewModelScope.launch { - deleteEntry(event.entry) - uiState = uiState.copy( - navigateUp = true - ) - } - is DiaryEvent.GetEntry -> viewModelScope.launch { - val entry = getEntry(event.entryId) - uiState = uiState.copy( - entry = entry, - readingMode = true - ) - } is DiaryEvent.SearchEntries -> viewModelScope.launch { val entries = searchEntries(event.query) uiState = uiState.copy( searchEntries = entries ) } - is DiaryEvent.UpdateEntry -> viewModelScope.launch { - updateEntry(event.entry) - uiState = uiState.copy( - navigateUp = true - ) - } is DiaryEvent.UpdateOrder -> viewModelScope.launch { savePreference( intPreferencesKey(PrefsConstants.DIARY_ORDER_KEY), event.order.toInt() ) } - DiaryEvent.ErrorDisplayed -> uiState = uiState.copy(error = null) is DiaryEvent.ChangeChartEntriesRange -> viewModelScope.launch { uiState = uiState.copy(chartEntries = getEntriesForChart { if (event.monthly) it.createdDate.inTheLast30Days() else it.createdDate.inTheLastYear() }) } - is DiaryEvent.ToggleReadingMode -> uiState = uiState.copy(readingMode = !uiState.readingMode) } } data class UiState( val entries: Map> = emptyMap(), val entriesOrder: Order = Order.DateModified(OrderType.ASC), - val entry: DiaryEntry? = null, - val error: String? = null, val searchEntries: List = emptyList(), - val navigateUp: Boolean = false, - val chartEntries : List = emptyList(), - val readingMode: Boolean = false + val chartEntries : List = emptyList() ) private fun getEntries(order: Order) { From 51dd258047a85f11761f90e9679deed8546a853d Mon Sep 17 00:00:00 2001 From: Mohamed Date: Thu, 3 Oct 2024 18:07:31 +0300 Subject: [PATCH 17/31] Add imePadding to Diary content text field --- .../java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt index 341b2788..d67293f2 100644 --- a/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt +++ b/diary/presentation/src/main/java/com/mhss/app/presentation/DiaryEntryDetailsScreen.kt @@ -161,6 +161,7 @@ fun DiaryEntryDetailsScreen( .fillMaxWidth() .weight(1f) .padding(bottom = 8.dp) + .imePadding() ) } } From d5ea8850a59621e5a4a33663064e865da20810cf Mon Sep 17 00:00:00 2001 From: Mohamed Date: Thu, 3 Oct 2024 18:08:04 +0300 Subject: [PATCH 18/31] Remove BackHandler in TasksScreen --- .../java/com/mhss/app/presentation/TasksScreen.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksScreen.kt b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksScreen.kt index 702a3ff1..bf69672d 100644 --- a/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksScreen.kt +++ b/tasks/presentation/src/main/java/com/mhss/app/presentation/TasksScreen.kt @@ -2,7 +2,6 @@ package com.mhss.app.presentation -import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -54,12 +53,6 @@ fun TasksScreen( } val alarmPermissionState = rememberPermissionState(Permission.SCHEDULE_ALARMS) val scope = rememberCoroutineScope() - BackHandler { - if (openSheet) - openSheet = false - else - navController.navigateUp() - } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { @@ -85,7 +78,10 @@ fun TasksScreen( ) { paddingValues -> if (openSheet) ModalBottomSheet( sheetState = sheetState, - onDismissRequest = { openSheet = false } + onDismissRequest = { openSheet = false }, + properties = ModalBottomSheetProperties( + shouldDismissOnBackPress = true + ) ) { AddTaskBottomSheetContent( onAddTask = { From 28df7e71fd5ded52b1f4a83a6b9093f671fc708d Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sat, 5 Oct 2024 20:25:24 +0300 Subject: [PATCH 19/31] Change assistant messages to mutableStateList and fix chat ui issues --- .../mhss/app/presentation/AssistantScreen.kt | 9 +++---- .../app/presentation/AssistantViewModel.kt | 24 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantScreen.kt b/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantScreen.kt index cc9dd5b3..334b778d 100644 --- a/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantScreen.kt +++ b/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantScreen.kt @@ -66,7 +66,7 @@ fun AssistantScreen( ) { val context = LocalContext.current val uiState = viewModel.uiState - val messages = uiState.messages + val messages = viewModel.messages val loading = uiState.loading val error = uiState.error var text by rememberSaveable { mutableStateOf("") } @@ -169,11 +169,12 @@ fun AssistantScreen( LeftToRight { LazyColumn( state = lazyListState, - reverseLayout = true + reverseLayout = true, + modifier = Modifier.fillMaxSize() ) { - item(key = 1) { Spacer(Modifier.height(20.dp)) } + item(key = -1) { Spacer(Modifier.height(20.dp)) } error?.let { error -> - item(key = 2) { + item(key = -2) { Card( shape = RoundedCornerShape(18.dp), border = BorderStroke( diff --git a/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantViewModel.kt b/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantViewModel.kt index cf703d84..1b72bc21 100644 --- a/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantViewModel.kt +++ b/ai/presentation/src/main/java/com/mhss/app/presentation/AssistantViewModel.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.koin.android.annotation.KoinViewModel -import java.util.LinkedList @KoinViewModel class AssistantViewModel( @@ -57,12 +56,11 @@ class AssistantViewModel( private val getTaskById: GetTaskByIdUseCase, ) : ViewModel() { - private val messages = ArrayList() + + private val _messages = mutableStateListOf() + val messages: List = _messages val attachments = mutableStateListOf() - // LinkedList to enable inserting at the beginning of the list efficiently - // LazyColumn needs the list in reverse order - private val uiMessages = LinkedList() var uiState by mutableStateOf(UiState()) private set @@ -136,17 +134,15 @@ class AssistantViewModel( attachments = event.message.attachments, attachmentsText = getAttachmentText(event.message.attachments) ) - messages.add(message) - uiMessages.addFirst(message) + _messages.add(0, message) attachments.clear() uiState = uiState.copy( - messages = uiMessages, loading = true, error = null ) val result = sendAiMessage( - messages, + _messages.reversed(), aiKey, aiModel, aiProvider.value, @@ -154,19 +150,16 @@ class AssistantViewModel( ) when (result) { is NetworkResult.Success -> { - messages.add(result.data) - uiMessages.addFirst(result.data) + _messages.add(0, result.data) - uiState = uiState.copy(messages = uiMessages, loading = false) + uiState = uiState.copy(loading = false) } is NetworkResult.Failure -> { - messages.removeLast() delay(300) - uiMessages.removeFirst() + _messages.removeAt(0) uiState = uiState.copy( - messages = uiMessages, loading = false, error = result ) @@ -254,7 +247,6 @@ class AssistantViewModel( data class UiState( - val messages: List = emptyList(), val loading: Boolean = false, val error: NetworkResult.Failure? = null, val noteView: ItemView = ItemView.LIST, From 7dccf00a343bccec0c62c306a98cb2a17508e946 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sat, 5 Oct 2024 21:32:24 +0300 Subject: [PATCH 20/31] Simplify and optimize GlowingBorder code --- .../presentation/components/AiResultSheet.kt | 3 +- .../presentation/components/GlowingBorder.kt | 48 ++++++++++--------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/ai/presentation/src/main/java/com/mhss/app/presentation/components/AiResultSheet.kt b/ai/presentation/src/main/java/com/mhss/app/presentation/components/AiResultSheet.kt index 0d0e0cf5..2a5d5618 100644 --- a/ai/presentation/src/main/java/com/mhss/app/presentation/components/AiResultSheet.kt +++ b/ai/presentation/src/main/java/com/mhss/app/presentation/components/AiResultSheet.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -104,7 +105,7 @@ fun AiResultSheet( val surfaceVariant = MaterialTheme.colorScheme.surfaceVariant Box( - Modifier.offset { + Modifier.wrapContentHeight().offset { if (loading) { IntOffset(0, offset) } else IntOffset.Zero diff --git a/ai/presentation/src/main/java/com/mhss/app/presentation/components/GlowingBorder.kt b/ai/presentation/src/main/java/com/mhss/app/presentation/components/GlowingBorder.kt index dc7999ae..6f8cee10 100644 --- a/ai/presentation/src/main/java/com/mhss/app/presentation/components/GlowingBorder.kt +++ b/ai/presentation/src/main/java/com/mhss/app/presentation/components/GlowingBorder.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.core.animateValue import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -17,9 +16,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -32,12 +36,12 @@ fun GlowingBorder( cornerRadius: Dp = 24.dp, innerPadding: PaddingValues = PaddingValues(0.dp), blur: Dp = 14.dp, - animationDuration: Int = 800 + animationDuration: Int = 800, ) { val infiniteTransition = rememberInfiniteTransition("glow") val borderWidth by infiniteTransition.animateValue( - initialValue = 5.dp, - targetValue = 12.dp, + initialValue = 7.dp, + targetValue = 14.dp, typeConverter = Dp.VectorConverter, animationSpec = infiniteRepeatable( animation = tween( @@ -48,26 +52,24 @@ fun GlowingBorder( ), label = "glowBorder" ) + val cornerRadiusPx = with(LocalDensity.current) { cornerRadius.toPx() } + val gradientBorder = remember { gradientBrushColor() } + val rectCornerRadius = remember { CornerRadius(cornerRadiusPx, cornerRadiusPx) } - Box(modifier) { - Box( - modifier = Modifier - .matchParentSize() - .blur(blur) - ) { - Box( - modifier = Modifier - .padding(innerPadding) - .matchParentSize() - .clip(RoundedCornerShape(cornerRadius)) - .border( - width = borderWidth, - brush = gradientBrushColor(), - shape = RoundedCornerShape(cornerRadius) - ) - ) - } - } + Box( + modifier = modifier + .blur(blur) + .padding(innerPadding) + .clip(RoundedCornerShape(cornerRadius)) + .drawBehind { + drawRoundRect( + brush = gradientBorder, + size = size, + cornerRadius = rectCornerRadius, + style = Stroke(width = borderWidth.toPx()) + ) + } + ) } @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) From 9958e4afd29e6063d389f425a3e8d8615bc326c8 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 20:09:34 +0300 Subject: [PATCH 21/31] Update android gradle plugin --- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 34fb7e32..9ce6eb42 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ lifecycleVersion = "2.8.6" navigationCompose = "2.8.1" room = "2.6.1" workManager = "2.9.1" -androidGradlePlugin = "8.6.1" +androidGradlePlugin = "8.7.0" kotlin = "2.0.20" ksp = "2.0.20-1.0.25" koinBOM = "4.0.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5272b7f7..5e58d7f6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Sep 21 12:40:08 EET 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a5c52bc08024b66e0f5078af135711632ea2435e Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 20:16:06 +0300 Subject: [PATCH 22/31] Update Compose BOM and navigation version --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ce6eb42..8ffee2c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ activityCompose = "1.9.2" appcompat = "1.7.0" biometric = "1.4.0-alpha02" calf = "0.5.5" -composeBom = "2024.09.02" +composeBom = "2024.09.03" composeMarkdown = "0.5.4" coreKtx = "1.13.1" coroutines = "1.9.0" @@ -15,7 +15,7 @@ junit = "4.13.2" junitVersion = "1.2.1" kotlinxSerializationJson = "1.7.3" lifecycleVersion = "2.8.6" -navigationCompose = "2.8.1" +navigationCompose = "2.8.2" room = "2.6.1" workManager = "2.9.1" androidGradlePlugin = "8.7.0" From 70e03dd7ed3d66573980fcf0168f734b83d146e2 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 20:32:13 +0300 Subject: [PATCH 23/31] Add dependencies: androidx-glance, androidx-glance-preview, androidx-glance-appwidget-preview --- gradle/libs.versions.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ffee2c5..d7af0708 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,10 @@ androidx-datastore-preferences = { module = "androidx.datastore:datastore-prefer androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "documentfile" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } androidx-glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "glanceAppwidget" } +androidx-glance = { module = "androidx.glance:glance", version.ref = "glanceAppwidget" } androidx-glance-material = { module = "androidx.glance:glance-material3", version.ref = "glanceAppwidget" } +androidx-glance-preview = { module = "androidx.glance:glance-preview", version.ref = "glanceAppwidget" } +androidx-glance-appwidget-preview = { module = "androidx.glance:glance-appwidget-preview", version.ref = "glanceAppwidget" } androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleVersion" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleVersion" } @@ -88,7 +91,7 @@ kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version. [bundles] koin = ["koin-core", "koin-annotations"] compose = ["compose-ui", "compose-material", "androidx-navigation-compose", "compose-ui-tooling", "androidx-lifecycle-runtime-compose", "compose-ui-tooling-preview", "koin-androidx-compose", "compose-markdown"] -androidx-glance = ["androidx-glance-appwidget", "androidx-glance-material"] +androidx-glance = ["androidx-glance", "androidx-glance-appwidget", "androidx-glance-material", "androidx-glance-preview", "androidx-glance-appwidget-preview"] ktor-core = ["ktor-core", "ktor-serialization", "ktor-content-negotiation", "ktor-logging"] From dd426a9b0865f64508d8633713ed654bdb6d8082 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 21:01:14 +0300 Subject: [PATCH 24/31] Set widget inner corner radius to (outer - padding) --- .../res/drawable/large_inner_item_rounded_corner_shape.xml | 4 ++-- .../mhss/app/widget/BackwardCompatibleRoundedBackground.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/res/drawable/large_inner_item_rounded_corner_shape.xml b/core/ui/src/main/res/drawable/large_inner_item_rounded_corner_shape.xml index d418abe4..0189e576 100644 --- a/core/ui/src/main/res/drawable/large_inner_item_rounded_corner_shape.xml +++ b/core/ui/src/main/res/drawable/large_inner_item_rounded_corner_shape.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/widget/src/main/java/com/mhss/app/widget/BackwardCompatibleRoundedBackground.kt b/widget/src/main/java/com/mhss/app/widget/BackwardCompatibleRoundedBackground.kt index 86fadc24..b858b85b 100644 --- a/widget/src/main/java/com/mhss/app/widget/BackwardCompatibleRoundedBackground.kt +++ b/widget/src/main/java/com/mhss/app/widget/BackwardCompatibleRoundedBackground.kt @@ -32,7 +32,7 @@ fun GlanceModifier.largeInnerBackgroundBasedOnVersion() = ) } else { background(GlanceTheme.colors.onSecondary) - .cornerRadius(20.dp) + .cornerRadius(17.dp) } @Composable From 5e56945ef27ff9bb8ca4a46e17412a8bd735b7b2 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 21:01:45 +0300 Subject: [PATCH 25/31] Update calendar widget xml preview --- core/ui/src/main/res/layout/calendar_preview_fake_item_1.xml | 2 +- core/ui/src/main/res/layout/calendar_preview_fake_item_2.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/res/layout/calendar_preview_fake_item_1.xml b/core/ui/src/main/res/layout/calendar_preview_fake_item_1.xml index bc2b5b07..fefcd001 100644 --- a/core/ui/src/main/res/layout/calendar_preview_fake_item_1.xml +++ b/core/ui/src/main/res/layout/calendar_preview_fake_item_1.xml @@ -12,7 +12,7 @@ android:text="@string/thursday_30_calendar_preview" android:padding="4dp" android:textColor="@color/white" - android:textSize="16sp" + android:textSize="12sp" android:textStyle="bold" /> From 4de787c8d77d6bcb6cde2588f86ce4f552a508cc Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 21:08:25 +0300 Subject: [PATCH 26/31] Add CalendarHomeScreenWidgetPreview --- .../calendar/CalendarHomeScreenWidget.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt b/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt index df969f12..6eca899f 100644 --- a/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt +++ b/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt @@ -1,6 +1,8 @@ package com.mhss.app.widget.calendar import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.glance.* @@ -9,14 +11,21 @@ import androidx.glance.appwidget.action.actionRunCallback import androidx.glance.appwidget.lazy.LazyColumn import androidx.glance.appwidget.lazy.items import androidx.glance.layout.* +import androidx.glance.material3.ColorProviders +import androidx.glance.preview.ExperimentalGlancePreviewApi +import androidx.glance.preview.Preview import androidx.glance.text.FontWeight import androidx.glance.text.Text import androidx.glance.text.TextAlign import androidx.glance.text.TextStyle import com.mhss.app.ui.R import com.mhss.app.domain.model.CalendarEvent +import com.mhss.app.util.date.now +import com.mhss.app.widget.WidgetTheme import com.mhss.app.widget.largeBackgroundBasedOnVersion import com.mhss.app.widget.largeInnerBackgroundBasedOnVersion +import com.mhss.app.widget.widgetDarkColorScheme +import kotlin.time.Duration.Companion.hours @Composable fun CalendarHomeScreenWidget( @@ -147,4 +156,48 @@ fun CalendarHomeScreenWidget( } } } +} + +@OptIn(ExperimentalGlancePreviewApi::class) +@Preview(widthDp = 360, heightDp = 240) +@Composable +private fun CalendarHomeScreenWidgetPreview() { + WidgetTheme(ColorProviders(widgetDarkColorScheme)) { + CalendarHomeScreenWidget( + mapOf( + "Monday 7, 2024" to listOf( + CalendarEvent( + id = 1, + title = "Event 1", + start = now(), + end = now() + 1.hours.inWholeMilliseconds, + calendarId = 1, + location = "Location 1", + color = Color.Red.toArgb() + ) + ), + "Tuesday 8, 2024" to listOf( + CalendarEvent( + id = 3, + title = "Event 2", + start = now() + 4.hours.inWholeMilliseconds, + end = now() + 5.hours.inWholeMilliseconds, + calendarId = 3, + location = "Location 2", + color = Color.Green.toArgb() + ), + CalendarEvent( + id = 4, + title = "Event 3", + start = now() + 6.hours.inWholeMilliseconds, + end = now() + 7.hours.inWholeMilliseconds, + calendarId = 4, + location = "Location 3", + color = Color.Gray.toArgb() + ) + ) + ), + true + ) + } } \ No newline at end of file From efbd7fb738fea9b9d78a9c33a2e304c2216e191b Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 6 Oct 2024 21:09:58 +0300 Subject: [PATCH 27/31] Update widgetDarkColorScheme colors --- widget/src/main/java/com/mhss/app/widget/WidgetTheme.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/main/java/com/mhss/app/widget/WidgetTheme.kt b/widget/src/main/java/com/mhss/app/widget/WidgetTheme.kt index 5031d03e..7cedb77b 100644 --- a/widget/src/main/java/com/mhss/app/widget/WidgetTheme.kt +++ b/widget/src/main/java/com/mhss/app/widget/WidgetTheme.kt @@ -9,8 +9,8 @@ import com.mhss.app.ui.theme.DarkGray import com.mhss.app.ui.theme.LightGray val widgetDarkColorScheme = darkColorScheme( - secondaryContainer = Color(0xFF0C0C0C), - onSecondary = Color.DarkGray, + secondaryContainer = Color(0xFF090909), + onSecondary = Color(0xFF1A1A1A), onSecondaryContainer = Color.White, secondary = Color.LightGray ) From 13d07b10e0d2138c7471490da9cb9b755e8e4f56 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 7 Oct 2024 10:11:56 +0300 Subject: [PATCH 28/31] Refine CalendarHomeScreenWidget UI --- .../widget/calendar/CalendarEventWidgetItem.kt | 16 +++++++--------- .../widget/calendar/CalendarHomeScreenWidget.kt | 9 +++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/widget/src/main/java/com/mhss/app/widget/calendar/CalendarEventWidgetItem.kt b/widget/src/main/java/com/mhss/app/widget/calendar/CalendarEventWidgetItem.kt index 8de52f9c..26aca650 100644 --- a/widget/src/main/java/com/mhss/app/widget/calendar/CalendarEventWidgetItem.kt +++ b/widget/src/main/java/com/mhss/app/widget/calendar/CalendarEventWidgetItem.kt @@ -69,15 +69,13 @@ fun CalendarEventWidgetItem( maxLines = 2 ) Spacer(GlanceModifier.height(4.dp)) - Text( - text = "${event.start.formatTime(context)} - ${event.end.formatTime(context)}", - style = TextStyle(color = GlanceTheme.colors.onSecondaryContainer) - ) - if (!event.location.isNullOrBlank()) { - Spacer(GlanceModifier.height(4.dp)) - Row( - verticalAlignment = Alignment.CenterVertically - ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "${event.start.formatTime(context)} - ${event.end.formatTime(context)}", + style = TextStyle(color = GlanceTheme.colors.onSecondaryContainer) + ) + Spacer(GlanceModifier.width(4.dp)) + if (!event.location.isNullOrBlank()) { Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(R.drawable.ic_location), diff --git a/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt b/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt index 6eca899f..deb4dced 100644 --- a/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt +++ b/widget/src/main/java/com/mhss/app/widget/calendar/CalendarHomeScreenWidget.kt @@ -91,9 +91,10 @@ fun CalendarHomeScreenWidget( modifier = GlanceModifier .fillMaxSize() .largeInnerBackgroundBasedOnVersion() - .padding(horizontal = 4.dp), + .padding(horizontal = 6.dp), horizontalAlignment = Alignment.Start ) { + item { Spacer(GlanceModifier.height(4.dp)) } if (events.isEmpty()) { item { Text( @@ -115,10 +116,10 @@ fun CalendarHomeScreenWidget( text = day, style = TextStyle( color = GlanceTheme.colors.secondary, - fontWeight = FontWeight.Normal, - fontSize = 14.sp + fontWeight = FontWeight.Bold, + fontSize = 12.sp ), - modifier = GlanceModifier.padding(bottom = 3.dp) + modifier = GlanceModifier.padding(bottom = 2.dp) ) } items(dayEvents) { event -> From 01a5666de375b2fe1c6ba1b2cc352a4f27fd9a7c Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 7 Oct 2024 11:34:52 +0300 Subject: [PATCH 29/31] Exclude widgets module from R8 --- app/proguard-rules.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 32bb9b17..3ba1e2e1 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -40,6 +40,7 @@ public static <1> INSTANCE; kotlinx.serialization.KSerializer serializer(...); } +-keep class com.mhss.app.widget.** { *; } -keepattributes RuntimeVisibleAnnotations,AnnotationDefault -dontwarn org.bouncycastle.jsse.BCSSLParameters From 7b84dc7e05241655dfa897f7c4517e9248efd485 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 7 Oct 2024 11:49:16 +0300 Subject: [PATCH 30/31] Add changelog 11 --- fastlane/metadata/android/en-US/changelogs/11.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/11.txt diff --git a/fastlane/metadata/android/en-US/changelogs/11.txt b/fastlane/metadata/android/en-US/changelogs/11.txt new file mode 100644 index 00000000..eaafea9f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/11.txt @@ -0,0 +1,5 @@ +- Added auto save to diary +- Fixed auto save doesn't work when closing the app +- Refined widgets UI +- Performance improvements +- Bug fixes \ No newline at end of file From 85855d3b4f4dbd5a5725e691e686d81ce2ce5072 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 7 Oct 2024 11:52:17 +0300 Subject: [PATCH 31/31] Bump app version --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b3213875..cb061aa4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.mhss.app.mybrain" minSdk = 26 targetSdk = 35 - versionCode = 10 - versionName = "2.0.1" + versionCode = 11 + versionName = "2.0.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables {