Skip to content

bugfix/reorder_modules #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 110 commits into
base: develop
Choose a base branch
from
Open

bugfix/reorder_modules #119

wants to merge 110 commits into from

Conversation

mshdabiola
Copy link
Owner

@mshdabiola mshdabiola commented Jun 15, 2025

PR Type

Enhancement, Bug fix


Description

  • Refactor notification dialog.

  • Update MainData to data class.

  • Improve main screen UI.

  • Update repositories and view models.


Changes walkthrough 📝

Relevant files
Enhancement
21 files
NoteficationDialog.kt
Refactor notification dialog to use new components and data structures
+619/-376
MainScreen.kt
Update MainScreen to use new notification dialog and top app bar
+57/-115
MainViewModel.kt
Update MainViewModel to handle new notification dialog and search
logic
+200/-207
TopbarAndDialog.kt
Create new top app bar and update search functionality     
+300/-62
DateTimeRepository.kt
Create DateTimeRepository to manage notification data       
+99/-0   
MainNavigatin.kt
Update MainNavigation to handle MainData type                       
+13/-12 
DetailScreen.kt
Update DetailScreen to use new notification dialog             
+21/-20 
NotificationInterval.kt
Create NotificationInterval sealed class                                 
+33/-0   
MainData.kt
Refactor MainData to data class                                                   
+9/-6     
SearchState.kt
Create SearchState sealed class                                                   
+16/-0   
NotificationUiState.kt
Create NotificationUiState data class                                       
+15/-0   
DateListUiState.kt
Add newValue field to DateListUiState                                       
+1/-0     
NotificationPlace.kt
Create NotificationPlace sealed class                                       
+8/-0     
NotificationTime.kt
Create NotificationTime data class                                             
+9/-0     
NotificationDate.kt
Create NotificationDate data class                                             
+8/-0     
DateUseCase.kt
Create DateUseCase class                                                                 
+7/-0     
modules_designsystem_arrays.xml
Add notification related string arrays                                     
+28/-0   
strings.xml
Add no search result string                                                           
+4/-0     
strings.xml
Add no search result string                                                           
+4/-0     
strings.xml
Add no search result string                                                           
+4/-0     
strings.xml
Add no search result string                                                           
+4/-0     
Bug fix
15 files
NotePadRepository.kt
Update NotePadRepository to handle MainData type                 
+27/-4   
UserPreferencesRepository.kt
Update UserPreferencesRepository to handle MainData type 
+3/-9     
MainState.kt
Update MainState to handle MainData type                                 
+3/-10   
FakeNotePadRepository.kt
Update FakeNotePadRepository to handle MainData type         
+3/-3     
OfflineFirstUserDataRepository.kt
Update OfflineFirstUserDataRepository to handle MainData type
+2/-1     
FakeUserDataRepository.kt
Update FakeUserDataRepository to handle MainData type       
+2/-1     
INotePadRepository.kt
Update INotePadRepository to handle MainData type               
+3/-2     
TestNotePadRepository.kt
Update TestNotePadRepository to handle MainData type         
+2/-2     
MainActivityViewModel.kt
Update MainActivityViewModel to handle MainData type         
+2/-1     
UserDataRepository.kt
Update UserDataRepository to handle MainData type               
+2/-1     
NotepadDao.kt
Update NotepadDao to handle MainData type                               
+5/-1     
SharedActivityViewModel.kt
Update SharedActivityViewModel to handle MainData type     
+1/-1     
TestUserDataRepository.kt
Update TestUserDataRepository to handle MainData type       
+1/-1     
LabelViewModel.kt
Update LabelViewModel to handle MainData type                       
+1/-1     
NoteApp.kt
Update NoteApp to handle MainData type                                     
+1/-1     
Dependencies
3 files
AndroidFeatureConventionPlugin.kt
Add :modules:domain dependency to AndroidFeatureConventionPlugin
+1/-0     
build.gradle.kts
Add dependencies for modules:data and modules:model           
+7/-0     
build.gradle.kts
Add kotlinx-datetime dependency                                                   
+3/-0     
Additional files
1 files
NewDialog.kt +0/-256 

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • This commit modifies the `setMainData` and `getNotePads` (renamed to `getNotePadsWithMainData`) functions in user data and notepad repositories to use the `MainData` enum instead of a `Long` or `NoteType`.
    
    - In `FakeUserDataRepository.kt`:
        - Changed the parameter type of `setMainData` from `Long` to `MainData`.
    - In `TestNotePadRepository.kt`:
        - Renamed `getNotePads` to `getNotePadsWithMainData`.
        - Changed the parameter type from `NoteType` to `MainData`.
    - In `FakeNotePadRepository.kt`:
        - Renamed `getNotePads` to `getNotePadsWithMainData`.
        - Changed the parameter type from `NoteType` to `MainData`.
        - The implementation now throws `NotImplementedError`.
    - In `TestUserDataRepository.kt`:
        - Changed the parameter type of `setMainData` from `Long` to `MainData`.
        - Updated the implementation to use `MainData.Note` when setting the main data in `currentUserData`.
    This commit refactors the `MainData` sealed class into a simple data class. It introduces a `noteType` property that derives its value from the `index` property. This change aims to simplify the structure and improve the readability of how different main data types are handled.
    
    -   **`modules/model/src/main/java/com/mshdabiola/model/MainData.kt`**:
        -   Changed `MainData` from a `sealed class` to a `data class` with a default `index` value corresponding to `NoteType.NOTE`.
        -   Removed the individual object/data class declarations for `Note`, `Achieve`, `Trash`, `Label`, and `Remainder`.
        -   Added a `noteType` read-only property of type `NoteType` (enum assumed to be defined elsewhere) which is determined by the value of `index`.
            -   `index == -1L` maps to `NoteType.NOTE`
            -   `index == -2L` maps to `NoteType.ARCHIVE`
            -   `index == -3L` maps to `NoteType.TRASH`
            -   `index == -4L` maps to `NoteType.REMAINDER`
            -   Any other `index` value maps to `NoteType.LABEL`
    This commit introduces a new `DateUseCase` class in the domain module and updates its dependencies.
    
    - In `modules/domain/src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt`:
        - Added a new class `DateUseCase` with an invoke operator that currently returns an empty string.
    - In `modules/domain/build.gradle.kts`:
        - Added `api` dependencies for `projects.modules.data` and `projects.modules.model`.
        - Added `testImplementation` dependency for `projects.modules.testing`.
    This commit refactors how `MainData` is handled in `UserPreferencesRepository.kt`.
    
    - The mapping of `it.mainScreenType` to `MainData` enum values has been replaced with a direct instantiation of `MainData(it.mainScreenType)`.
    - The `setMainData` function now accepts a `MainData` object as a parameter instead of a `Long` index. The `mainScreenType` in `UserPreferences` is updated using `mainData.index`.
    This commit updates `NotepadDao.kt` to enhance functionality for retrieving notes.
    
    - Renamed `getListOfNotePad` to `getListOfNotePadByNoteType` for clarity.
    - Added a new function `getListOfNotePadByReminder` to fetch notes that have a reminder set (`reminder > 0`), ordered by ID in descending order.
    …nData`
    
    This commit updates `NotePadRepository` and `UserDataRepository` to utilize the `MainData` model class for managing note types and user preferences, enhancing type safety and clarity.
    
    **Key changes:**
    
    -   **`NotePadRepository.kt`**:
        -   Replaced `getNotePads(noteType: NoteType)` with `getNotePadsWithMainData(mainData: MainData)`.
        -   The new method `getNotePadsWithMainData` now filters notes based on `mainData.noteType`.
            -   For `NoteType.LABEL`, it filters notes where any label ID matches `mainData.index`.
            -   For `NoteType.REMAINDER`, it fetches notes by reminder.
            -   For other `NoteType`s, it fetches notes by `mainData.noteType`.
        -   Updated `deleteTrashType()` to use `getNotePadsWithMainData(MainData(NoteType.TRASH.index))` to fetch trash items.
    -   **`INotePadRepository.kt`**:
        -   Updated the interface to reflect the change from `getNotePads(noteType: NoteType)` to `getNotePadsWithMainData(mainData: MainData)`.
    -   **`UserDataRepository.kt`**:
        -   Modified `setMainData(mainData: Long)` to `setMainData(mainData: MainData)`.
    -   **`OfflineFirstUserDataRepository.kt`**:
        -   Updated the implementation of `setMainData` to accept a `MainData` object.
    This commit refactors the `MainState` and related logic in `MainViewModel` and `MainScreen`. The primary change is the removal of search-specific properties and logic from `MainState` and the direct handling of notepad filtering based on `MainData` within `MainViewModel`.
    
    **Key changes:**
    
    -   **`feature/main/MainState.kt`**:
        -   Removed `isSearch`, `noteType`, `types`, `color`, `label`, and `searchSort` properties from `MainState.Success`.
        -   Removed `Empty` and `Finish` sealed class instances.
        -   The `mainData` property in `MainState.Success` now defaults to `MainData()` instead of `MainData.Note`.
    -   **`feature/main/MainScreen.kt`**:
        -   Removed parameters related to search functionality: `searchState`, `toggleSearch`, and `onSetSearch` from `MainRoute`, `MainScreen`, and `SuccessState` composables.
        -   Removed `SearchTopBar` usage.
        -   `MainTopAppBar`'s `navigateToSearch` parameter is now a no-op lambda.
        -   Removed the logic for displaying search filter options (Types, Labels, Colors) when `isSearch` was true and `notePads` were empty.
        -   `EmptyState` now takes `mainData: MainData` instead of `noteType: NoteType`.
        -   The condition for displaying `EmptyState` in `SuccessState` now checks `success.notePads.isEmpty()` instead of `!success.isSearch && success.notePads.isEmpty()`.
        -   TopAppBar selection logic (`LabelTopAppBar`, `MainTopAppBar`, etc.) now directly uses `success.mainData.noteType` instead of `success.noteType`.
    -   **`feature/main/MainViewModel.kt`**:
        -   Removed `searchState` (`TextFieldState`).
        -   The `mainState` is now a `StateFlow` derived from combining `currentNotepads` (filtered by `MainData` from `userDataRepository`), `userDataRepository.userData.mainData`, and `setOfSelected` (a new `MutableStateFlow<Set<Long>>`). This simplifies state management by removing the complex `combine` logic that handled search and notepad filtering.
        -   Removed `toggleSearch` and `onSetSearch` functions.
        -   `onSelectCard` and `clearSelected` now update the `setOfSelected` `MutableStateFlow` directly.
        -   `setNoteType` function's body is cleared as `noteType` is no longer part of `MainState`.
        -   Removed the initial `viewModelScope.launch` blocks that were collecting `userData` and combining `mainState`, `searchState.text`, and `notepadRepository.getNotePads()` to update `_mainState`. The new `mainState` flow handles this reactively.
    This commit updates the `LabelViewModel.kt` to call `userDataRepository.setMainData(MainData())` instead of `userDataRepository.setMainData(MainData.Note.index)` when a label is deleted.
    
    - In `feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt`:
        - Changed the argument passed to `userDataRepository.setMainData` from `MainData.Note.index` to `MainData()` within the `deleteLabel` function.
    This commit updates the main navigation logic to use a `MainData` object instead of a `Long` to represent the current navigation argument. This improves type safety and clarity.
    
    **Key changes:**
    
    -   **`SharedActivityViewModel.kt`**:
        -   Updated the default `mainData` in `UiState` to be a `MainData()` object.
    -   **`MainActivityViewModel.kt`**:
        -   Modified `setMainData` function to accept a `MainData` object.
    -   **`MainNavigatin.kt`**:
        -   Changed the type of `currentMainArg` from `Long` to `MainData`.
        -   Updated `onNavigation` lambda to accept a `MainData` object.
        -   Modified selection logic for `NavigationDrawerItem`s to compare with `currentMainArg.noteType` or `currentMainArg.index` based on the item type.
        -   Updated `onClick` handlers to pass `MainData` objects.
    -   **`NoteApp.kt`**:
        -   Updated the `currentMainArg` in `MainNavigation` to use `userData.mainData` directly, providing a `MainData()` default.
    This commit updates the `AndroidFeatureConventionPlugin.kt` to include the `:modules:domain` project as an implementation dependency for Android feature modules.
    
    - In `build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt`:
        - Added `add("implementation", project(":modules:domain"))` to the dependencies block.
    This commit introduces a new search experience in the `MainScreen` by replacing the `MainTopAppBar` with `NewTopMainAppBar`. This change enables an expandable search bar and displays search results, including notes, types, labels, and colors.
    
    **Key changes:**
    
    -   **`feature/main/SearchState.kt`**:
        -   Created a new sealed class `SearchState` (`Loading`, `Success`) to manage the state of the search UI, including search results and filter options.
    -   **`feature/main/MainState.kt`**:
        -   Removed unused search-related properties (`types`, `color`, `label`, `searchSort`) as they are now handled by `SearchState`.
    -   **`feature/main/MainScreen.kt`**:
        -   Integrated `SearchState` and `searchQuery` (a `TextFieldState`) from `MainViewModel`.
        -   Replaced `MainTopAppBar` with `NewTopMainAppBar` in `MainScreenContent` and `MainContent`.
        -   Passed search-related state and callbacks (`searchState`, `searchQuery`, `onSetSearch`, `onExpandSearch`) to `NewTopMainAppBar`.
        -   Removed the old search UI elements (LabelBox for types, labels, and colors) as this functionality is now part of the `NewTopMainAppBar`'s expanded state.
    -   **`feature/main/MainViewModel.kt`**:
        -   Added `searchQuery` (`TextFieldState`) to manage the search input.
        -   Introduced `searchTriple` (`MutableStateFlow`) to hold available search filter options (types, colors, labels).
        -   Added `searchSort` (`MutableStateFlow`) to store the currently applied search filter.
        -   Created `searchState` (`StateFlow<SearchState>`) by combining `searchQuery`, `notepadRepository.getNotePads()`, `searchTriple`, and `searchSort` to reactively update search results and filter options.
        -   Implemented `onSearch(SearchState.Success)` to filter notes based on the search query and selected filters.
        -   Added `onExpandSearch(isExpand: Boolean)` to populate `searchTriple` with available filter options when the search bar is expanded and clear them when collapsed.
        -   Added `onSetSearch(searchSort: SearchSort?)` to update the `searchSort` state.
        -   Removed `setNoteType(noteType: NoteType)` as it's no longer used.
    -   **`feature/main/TopbarAndDialog.kt`**:
        -   Renamed the existing `MainTopAppBar` to `OldMainTopAppBar` (though not directly used in this commit, it's a preparatory step).
        -   Introduced `NewTopMainAppBar` composable which uses `TopSearchBar` and `ExpandedFullScreenSearchBar` from Material 3 to provide an expandable search interface.
            -   When collapsed, it shows navigation, search placeholder, and grid toggle.
            -   When expanded, it shows a back button, clear text button, and the search input field.
            -   The expanded view displays search results (`searchState.searches`) or filter options (`searchState.types`, `searchState.label`, `searchState.color`) using `LazyColumn` and `LabelBox`.
        -   Added `NewTopMainAppBarPreview`.
        -   The old `MainTopAppBar` was kept but is no longer the primary top app bar for the main screen.
    This commit refactors the search functionality and UI in the main feature.
    
    - In `MainViewModel.kt`:
        - Removed filtering of `notepads` by query in the `combine` block for `searchState`. The `notepads` are now passed directly.
        - Added debug print statements in `onSearch` to log the search query.
    - In `TopbarAndDialog.kt`:
        - Removed unused import `android.view.Surface`.
        - Replaced `LazyColumn` with `LazyVerticalStaggeredGrid` for displaying search results in `SearchContent`.
        - `LabelBox` and color filter sections are now items with `StaggeredGridItemSpan.FullLine`.
        - Utilized the `noteItems` extension function to display search results, removing the direct usage of `NoteCard` within the `LazyColumn`'s `items` block.
    This commit introduces a new string resource `modules_designsystem_no_result` with the value "No search result".
    
    This resource has been added to the following files:
    - `modules/designsystem/src/main/res/values/strings.xml`
    - `modules/designsystem/src/main/res/values-fr/strings.xml`
    - `modules/designsystem/src/main/res/values-ru/strings.xml`
    - `modules/designsystem/src/main/res/values-v23/strings.xml`
    This commit enhances the search experience in the Main feature by refining the search logic and updating the UI to provide better feedback to the user.
    
    - In `feature/main/MainViewModel.kt`:
        - Introduced `isTextAfterSearchSort` boolean to track if text input occurs after a sort operation in search.
        - Modified `searchState` flow to remove debug `println` statements.
        - Updated `onSearch` function:
            - Removed debug `println` statements.
            - If text is entered after a sort (`isTextAfterSearchSort = true`), and then the search query is cleared, the search sort is reset by calling `onSetSearch(null)`.
    - In `feature/main/TopbarAndDialog.kt`:
        - Updated the search results display in `SearchContent`:
            - When `searchQuery.text` is not blank and `searchState.searches` is empty, it now displays an icon and "No result" text.
            - When `searchQuery.text` is blank and `searchState.searches` is empty, it proceeds to display type and label filters if available.
    This commit updates how `SearchSort` items are displayed in `MainScreen.kt`.
    
    - In `MainScreen.kt`:
        - Changed `horizontalArrangement` of `Layout` from `Arrangement.SpaceAround` to `Arrangement.Start`.
        - Updated the display name for `SearchSort.Label` to use `searchSort.name` instead of a string resource.
    This commit introduces improvements to the search functionality and card layout within the main feature.
    
    - In `feature/main/MainScreen.kt`:
        - The `items` extension function for `LazyStaggeredGridScope` now accepts a `sharedName` parameter (defaulting to "note") to customize the `sharedContentState` key, allowing for distinct shared element transitions.
        - Updated the `horizontalArrangement` in `LazyVerticalStaggeredGrid` within the `NoteCard` composable to use `Arrangement.spacedBy(32.dp, Alignment.Start)` for better spacing between items.
    
    - In `feature/main/TopbarAndDialog.kt`:
        - Corrected the `onClick` lambda for color selection in the search dialog to use the iterated `color` object (`onSetSearch(color)`) instead of the `ColorPalette` object.
        - Passed `sharedName = "search"` to the `items` call within the search results display to differentiate shared element transitions from the main note list.
    This commit introduces a significant refactoring of the notification dialog and its associated UI components and state management. The old `NewDialog.kt` has been removed, and `NoteficationDialog.kt` has been extensively rewritten to support a more robust and flexible notification setting experience.
    
    **Key Changes:**
    
    -   **`modules/ui/src/main/kotlin/com/mshdabiola/ui/NewDialog.kt`**:
        -   Deleted this file as its functionality is superseded by the changes in `NoteficationDialog.kt`.
    
    -   **`modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteficationDialog.kt`**:
        -   Renamed `NotificationDialogNew` to `NotificationDialogNew` (this might be a typo and intended to be something like `NotificationDialog`).
        -   Introduced a new `NotificationContent` composable that uses a `HorizontalPager` to switch between "Time" and "Place" based notification settings.
        -   Added new composables for selecting time, date, and interval:
            -   `TimeTextDropbox`: For selecting predefined times or picking a custom time.
            -   `DateTextDropbox`: For selecting predefined dates or picking a custom date.
            -   `IntervalTextDropbox`: For selecting notification recurrence intervals.
        -   Added `NotificationPlace` composable for managing place-based notifications, allowing selection from predefined places or adding a custom one.
        -   The dialog now utilizes `DatePickerDialog` and `TimePicker` for custom date and time selection.
        -   The UI state is managed by a `NotificationUiState` data class which includes details for time, date, interval, and place.
        -   Updated previews to reflect the new dialog structure and state.
    
    -   **`modules/ui/src/main/kotlin/com/mshdabiola/ui/state/DateListUiState.kt`**:
        -   Added a `newValue: Long = 1` property to the `DateListUiState` data class. This field's purpose is not immediately clear from the diff alone but suggests a change in how date/time values are handled.
    This commit introduces data models to support the notification UI.
    
    - **`modules/model/src/main/java/com/mshdabiola/model/`**:
        - Added `NotificationUiState.kt`: Represents the overall state of the notification UI, including current time, date, interval, place, and lists of available options for each.
        - Added `NotificationTime.kt`: Represents a specific time for a notification, with flags for picking a custom time and enabling/disabling.
        - Added `NotificationInterval.kt`: Defines various repeat intervals for notifications (Daily, Weekly, Monthly, Yearly, DoNotRepeat, Custom) and includes an `IntervalEnd` sealed class (Forever, EndDate, NumberOfTimes).
        - Added `NotificationPlace.kt`: Represents predefined notification places (Home, Work, School) and an option for a custom editable place.
        - Added `NotificationDate.kt`: Represents a specific date for a notification, with a flag for picking a custom date.
    - **`modules/model/build.gradle.kts`**:
        - Added `kotlinx-datetime` as an API dependency to support date and time operations within the new models.
    This commit introduces new string arrays to `modules_designsystem_arrays.xml` for configuring notification options.
    
    - Added `modules_designsystem_notification_days` for selecting notification day.
    - Added `modules_designsystem_notification_times` for selecting notification time.
    - Added `modules_designsystem_notification_interval` for setting notification repetition.
    - Added `modules_designsystem_notification_places` for selecting notification location.
    This commit introduces `DateTimeRepository` to manage the UI state for notification settings, including date, time, interval, and place.
    
    - Created `DateTimeRepository.kt` in `modules/data/src/main/kotlin/com/mshdabiola/data/repository/`.
    - The repository uses a `MutableStateFlow` (`_notificationUiState`) to hold `NotificationUiState`.
    - `initialize()`: Sets up the initial notification UI state with predefined and current date/time values. It populates lists for times, dates, intervals, and places.
    - `setPlace()`, `setDate()`, `setTime()`, and `setInterval()`: Functions to update the respective parts of the `NotificationUiState`.
    This commit replaces the separate `DateDialog` and `TimeDialog` composables with a new, unified `NotificationDialogNew` in both `DetailScreen.kt` and `MainScreen.kt`.
    
    - In `feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt`:
        - Commented out the existing `TimeDialog` and `DateDialog` invocations.
        - Added `NotificationDialogNew` with placeholder data and callbacks. The `dateDialogUiData` usage and specific view model calls for alarm, time, date, interval, and deletion are commented out.
    - In `feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt`:
        - Commented out the existing `TimeDialog` and `DateDialog` invocations and the `dateDialogUiData` collection.
        - Added `NotificationDialogNew` with placeholder data and callbacks. The `dateDialogUiData` usage and specific view model calls for alarm settings are commented out.
    This commit removes unused imports and applies code formatting to several files across the `feature/detail`, `feature/main`, `modules/model`, `modules/data`, and `modules/ui` modules.
    
    **Key changes:**
    
    -   **`feature/detail/DetailScreen.kt`**:
        -   Removed unused imports: `com.mshdabiola.ui.DateDialog` and `com.mshdabiola.ui.TimeDialog`.
        -   Formatted the `onDeleteAlarm` lambda in the `NotificationDialogNewPreview` composable.
    -   **`feature/main/MainScreen.kt`**:
        -   Removed unused imports: `com.mshdabiola.ui.DateDialog` and `com.mshdabiola.ui.TimeDialog`.
        -   Formatted the `onDeleteAlarm` lambda in the `NotificationDialogNewPreview` composable.
    -   **`modules/model`**:
        -   Formatted `NotificationUiState.kt`, `NotificationTime.kt`, `NotificationInterval.kt`, `NotificationPlace.kt`, and `NotificationDate.kt` for consistent spacing and structure.
    -   **`modules/data/repository/DateTimeRepository.kt`**:
        -   Applied code formatting for consistency.
    -   **`modules/ui/state/DateListUiState.kt`**:
        -   Formatted for consistent spacing.
    -   **`modules/ui/NoteficationDialog.kt`**:
        -   Applied code formatting for consistency, including lambda parameters and composable structures.
        -   Minor adjustments to spacing and line breaks in `NotificationContent`, `NotificationPlace`, `TimeTextDropbox`, `DateTextDropbox`, and `IntervalTextDropbox` composables.
    Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Code Complexity

    The NotificationDialogNew function and its related composables are quite extensive and could benefit from refactoring to improve readability and maintainability. Consider breaking down the large functions into smaller, more focused ones.

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun NotificationDialogNew(
        notificationUiState: NotificationUiState,
        isEdit: Boolean = false,
        showDialog: Boolean = false,
        onDismissRequest: () -> Unit = {},
        onSetAlarm: () -> Unit = { },
        onDeleteAlarm: () -> Unit = {},
    
    ) {
        var showDateDialog by remember {
            mutableStateOf(false)
        }
        var showTimeDialog by remember {
            mutableStateOf(false)
        }
        var isError by remember {
            mutableStateOf(false)
        }
    
        if (showDialog) {
            AlertDialog(
                onDismissRequest = onDismissRequest,
                title = { Text(text = if (isEdit) "Edit Reminder" else "Add Reminder") },
                text = {
                },
                confirmButton = {
                    Button(
                        onClick = {
                            onSetAlarm()
                            onDismissRequest()
                        },
                        enabled = !isError,
                    ) {
                        Text(text = "Save")
                    }
                },
                dismissButton = {
                    Row {
                        if (isEdit) {
                            TextButton(
                                onClick = {
                                    onDismissRequest()
                                    onDeleteAlarm()
                                },
                            ) {
                                Text(text = "Delete")
                            }
                            Spacer(modifier = Modifier.width(8.dp))
                        }
                        TextButton(onClick = { onDismissRequest() }) {
                            Text(text = "Cancel")
                        }
                    }
                },
            )
            if (showDateDialog) {
                val dateState = rememberDatePickerState()
    
                DatePickerDialog(
                    onDismissRequest = {
                        showDateDialog = false
                    },
                    confirmButton = {
                        Button(
                            onClick = {
                                showDateDialog = false
                            },
                        ) {
                            Text(text = "Set date")
                        }
                    },
                    dismissButton = {
                        TextButton(
                            onClick = {
                                showDateDialog = false
                            },
                        ) {
                            Text(text = "Cancel")
                        }
                    },
                ) {
                    DatePicker(
                        state = dateState,
                        //   dateValidator = { it > (System.currentTimeMillis() - (48 * 60 * 60 * 1000)) }
                    )
                }
            }
            if (showTimeDialog) {
                val timeState = rememberTimePickerState()
                DatePickerDialog(
                    onDismissRequest = onDismissRequest,
                    confirmButton = {
                        Button(
                            onClick = {
                                showTimeDialog = false
                            },
                        ) {
                            Text(text = "Set time")
                        }
                    },
                    dismissButton = {
                        TextButton(
                            onClick = {
                                showTimeDialog = false
                            },
                        ) {
                            Text(text = "Cancel")
                        }
                    },
                ) {
                    TimePicker(state = timeState)
                }
            }
        }
    }
    
    @SuppressLint("NewApi")
    @OptIn(ExperimentalMaterial3Api::class)
    @Preview
    @Composable
    fun NotificationDialogNewPreview() {
        val notificationUiState = NotificationUiState(
            currentTime = LocalTime(10, 30),
            currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date,
            currentInterval = NotificationInterval.Daily(
                interval = 1,
                intervalEnd = IntervalEnd.Forever,
            ),
            currentPlace = NotificationPlace.Home,
            times = listOf(
                NotificationTime(LocalTime(9, 0), false, true),
                NotificationTime(LocalTime(12, 0), false, true),
                NotificationTime(LocalTime(15, 0), false, true),
                NotificationTime(LocalTime(18, 0), false, true),
                NotificationTime(LocalTime(0, 0), true, true),
            ),
            dates = listOf(
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.plus(
                        DatePeriod(days = 0),
                    ),
                    false,
                ),
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.plus(
                        DatePeriod(days = 1),
                    ),
                    false,
                ),
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.plus(
                        DatePeriod(days = 7),
                    ),
                    false,
                ),
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date,
                    true,
                ),
            ),
            intervals = listOf(
                NotificationInterval.DoNotRepeat,
                NotificationInterval.Daily(1, IntervalEnd.Forever),
                NotificationInterval.Weekly(1, emptyList(), IntervalEnd.Forever),
                NotificationInterval.Monthly(1, true, IntervalEnd.Forever),
                NotificationInterval.Yearly(1, IntervalEnd.Forever),
                NotificationInterval.Custom,
            ),
            places = listOf(
                NotificationPlace.Home,
                NotificationPlace.Work,
                NotificationPlace.School,
            ),
        )
        NotificationDialogNew(notificationUiState = notificationUiState, showDialog = true)
    }
    
    @Composable
    private fun NotificationContent(
        modifier: Modifier = Modifier,
        notificationUiState: NotificationUiState,
    ) {
        val pagerState = rememberPagerState { 2 }
        Column(modifier.fillMaxSize()) {
            NoteTabRow(pagerState.currentPage) {
                NoteTab(pagerState.currentPage == 0, onClick = {}) {
                    Text(text = "Time")
                }
                NoteTab(pagerState.currentPage == 1, onClick = {}) {
                    Text(text = "Place")
                }
            }
            HorizontalPager(modifier = Modifier.fillMaxSize().weight(1f), state = pagerState) {
                when (it) {
                    0 -> {
                        Column(
                            Modifier.fillMaxSize(),
                            verticalArrangement = Arrangement.spacedBy(8.dp),
    
                        ) {
                            TimeTextDropbox(
                                modifier = Modifier.fillMaxWidth(),
                                currentTime = notificationUiState.currentTime,
                                onValueChange = {
                                },
                                times = notificationUiState.times,
                            )
                            DateTextDropbox(
                                modifier = Modifier.fillMaxWidth(),
                                currentDate = notificationUiState.currentDate,
                                onValueChange = {
                                },
                                dates = notificationUiState.dates,
                            )
                            IntervalTextDropbox(
                                modifier = Modifier.fillMaxWidth(),
                                currentInterval = notificationUiState.currentInterval,
                                onValueChange = {
                                },
                                notificationIntervals = notificationUiState.intervals,
                            )
                        }
                    }
    
                    1 -> {
                        NotificationPlace(
                            places = notificationUiState.places,
                            onValueChange = {
                            },
                            currentPlace = notificationUiState.currentPlace,
                        )
                    }
                }
            }
        }
    }
    
    @Preview(backgroundColor = 0xFF968F8F)
    @Composable
    fun NotificationContentPreview() {
        val notificationUiState = NotificationUiState(
            currentTime = LocalTime(10, 30),
            currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date,
            currentInterval = NotificationInterval.Daily(
                interval = 1,
                intervalEnd = IntervalEnd.Forever,
            ),
            currentPlace = NotificationPlace.Home,
            times = listOf(
                NotificationTime(LocalTime(9, 0), false, true),
                NotificationTime(LocalTime(12, 0), false, true),
                NotificationTime(LocalTime(15, 0), false, true),
                NotificationTime(LocalTime(18, 0), false, true),
                NotificationTime(LocalTime(0, 0), true, true),
            ),
            dates = listOf(
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.plus(
                        DatePeriod(days = 0),
                    ),
                    false,
                ),
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.plus(
                        DatePeriod(days = 1),
                    ),
                    false,
                ),
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.plus(
                        DatePeriod(days = 7),
                    ),
                    false,
                ),
                NotificationDate(
                    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date,
                    true,
                ),
            ),
            intervals = listOf(
                NotificationInterval.DoNotRepeat,
                NotificationInterval.Daily(1, IntervalEnd.Forever),
                NotificationInterval.Weekly(1, emptyList(), IntervalEnd.Forever),
                NotificationInterval.Monthly(1, true, IntervalEnd.Forever),
                NotificationInterval.Yearly(1, IntervalEnd.Forever),
                NotificationInterval.Custom,
            ),
            places = listOf(NotificationPlace.Home, NotificationPlace.Work, NotificationPlace.School),
        )
        NotificationContent(notificationUiState = notificationUiState)
    }
    
    @Composable
    fun NotificationPlace(
        modifier: Modifier = Modifier,
        places: List<NotificationPlace> = emptyList(),
        onValueChange: (NotificationPlace) -> Unit = {},
        currentPlace: NotificationPlace? = null,
    ) {
        val placeStringArray = stringArrayResource(R.array.modules_designsystem_notification_places)
        Column(modifier = modifier) {
            places.forEachIndexed { index, place ->
                if (place !is NotificationPlace.Edit) {
                    Row {
                        RadioButton(selected = place == currentPlace, onClick = {})
                        Text(text = placeStringArray[index])
                    }
                } else {
                    Row {
                        RadioButton(selected = place == currentPlace, onClick = { })
                        TextField(
                            state = rememberTextFieldState(),
                        )
                    }
                }
            }
        }
    }
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun TimeTextDropbox(
        modifier: Modifier = Modifier,
        currentTime: LocalTime,
        onValueChange: (NotificationTime) -> Unit = {},
        times: List<NotificationTime> = emptyList(),
    ) {
        var expanded by remember {
            mutableStateOf(false)
        }
        var showError by remember {
            mutableStateOf(false)
        }
        val state = rememberTextFieldState()
        LaunchedEffect(key1 = currentTime) {
            state.clearText()
            state.edit {
                append("${currentTime.hour}:${currentTime.minute} ")
                append(if (currentTime.hour < 12) "AM" else "PM")
            }
            val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
            showError = currentTime <= now.time
        }
    
        val timeStringArray = stringArrayResource(R.array.modules_designsystem_notification_times)
    
        ExposedDropdownMenuBox(
            modifier = modifier,
            expanded = expanded,
            onExpandedChange = { expanded = !expanded },
        ) {
            TextField(
                modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.SecondaryEditable, true),
                readOnly = true,
                state = state,
                supportingText = { if (showError) Text(text = "Time as past") },
                isError = showError,
                trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
                colors = ExposedDropdownMenuDefaults.textFieldColors(),
                lineLimits = TextFieldLineLimits.SingleLine,
    
            )
            ExposedDropdownMenu(
                expanded = expanded,
                onDismissRequest = {
                    expanded = false
                },
            ) {
                times.forEachIndexed { index, notificationTime ->
                    DropdownMenuItem(
                        text = { Text(text = timeStringArray[index]) },
                        onClick = {
                            onValueChange(notificationTime)
                            expanded = false
                        },
                        enabled = notificationTime.isEnable,
                        trailingIcon = {
                            Text(
                                text = if (notificationTime.isPickTime) {
                                    ""
                                } else {
                                    "${notificationTime.time.hour}:${notificationTime.time.minute} " +
                                        if (notificationTime.time.hour < 12) "AM" else "PM"
                                },
    
                            )
                        },
                    )
                }
            }
        }
    }
    
    @Preview
    @Composable
    fun TimeTextDropboxPreview() {
        val currentTime = LocalTime(10, 30)
        val times = listOf(
            NotificationTime(LocalTime(9, 0), false, true),
            NotificationTime(LocalTime(12, 0), false, true),
            NotificationTime(LocalTime(15, 0), false, true),
            NotificationTime(LocalTime(18, 0), false, true),
            NotificationTime(LocalTime(0, 0), true, true),
        )
        TimeTextDropbox(currentTime = currentTime, times = times)
    }
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun DateTextDropbox(
        modifier: Modifier = Modifier,
        currentDate: LocalDate,
        onValueChange: (NotificationDate) -> Unit = {},
        dates: List<NotificationDate> = emptyList(),
    ) {
        var expanded by remember {
            mutableStateOf(false)
        }
    
        val state = rememberTextFieldState()
        LaunchedEffect(key1 = currentDate) {
            state.clearText()
            state.edit {
                append("${currentDate.month.name} ${currentDate.dayOfMonth}")
                val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
                if (currentDate.year != now.year) {
                    append(", ${currentDate.year}")
                }
            }
        }
    
        val dateStringArray = stringArrayResource(R.array.modules_designsystem_notification_days)
    
        ExposedDropdownMenuBox(
            modifier = modifier,
            expanded = expanded,
            onExpandedChange = { expanded = !expanded },
        ) {
            TextField(
                modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.SecondaryEditable, true),
                readOnly = true,
                state = state,
                trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
                colors = ExposedDropdownMenuDefaults.textFieldColors(),
                lineLimits = TextFieldLineLimits.SingleLine,
    
            )
            ExposedDropdownMenu(
                expanded = expanded,
                onDismissRequest = {
                    expanded = false
                },
            ) {
                dates.forEachIndexed { index, notificationTime ->
                    DropdownMenuItem(
                        text = { Text(text = dateStringArray[index]) },
                        onClick = {
                            onValueChange(notificationTime)
                            expanded = false
                        },
                    )
                }
            }
        }
    }
    
    @Preview
    @Composable
    fun DateTextDropboxPreview() {
        val currentTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
        val dates = listOf(
            NotificationDate(currentTime.plus(DatePeriod(days = 0)), false),
            NotificationDate(currentTime.plus(DatePeriod(days = 1)), false),
            NotificationDate(currentTime.plus(DatePeriod(days = 7)), false),
            NotificationDate(currentTime, true),
        )
        DateTextDropbox(
            currentDate = currentTime,
            dates = dates,
        )
    }
    
    @Preview
    @Composable
    fun IntervalTextDropboxPreview() {
        val currentInterval = NotificationInterval.Daily(
            interval = 1,
            intervalEnd = IntervalEnd.Forever,
        )
        val notificationIntervals = listOf(
            NotificationInterval.DoNotRepeat,
            NotificationInterval.Daily(1, IntervalEnd.Forever),
            NotificationInterval.Weekly(1, emptyList(), IntervalEnd.Forever),
            NotificationInterval.Monthly(1, true, IntervalEnd.Forever),
            NotificationInterval.Yearly(1, IntervalEnd.Forever),
            NotificationInterval.Custom,
        )
        IntervalTextDropbox(
            currentInterval = currentInterval,
            notificationIntervals = notificationIntervals,
        )
    }
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun IntervalTextDropbox(
        modifier: Modifier = Modifier,
        currentInterval: NotificationInterval,
        onValueChange: (NotificationInterval) -> Unit = {},
        notificationIntervals: List<NotificationInterval> = emptyList(),
    ) {
        var expanded by remember {
            mutableStateOf(false)
        }
    
        val state = rememberTextFieldState()
        LaunchedEffect(key1 = currentInterval) {
            state.clearText()
            state.edit {
            }
        }
    
        val intervalStringArray = stringArrayResource(
            R.array.modules_designsystem_notification_interval,
        )
    
        ExposedDropdownMenuBox(
            modifier = modifier,
            expanded = expanded,
            onExpandedChange = { expanded = !expanded },
        ) {
            TextField(
                modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.SecondaryEditable, true),
                readOnly = true,
                state = state,
                trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
                colors = ExposedDropdownMenuDefaults.textFieldColors(),
                lineLimits = TextFieldLineLimits.SingleLine,
    
            )
            ExposedDropdownMenu(
                expanded = expanded,
                onDismissRequest = {
                    expanded = false
                },
            ) {
                notificationIntervals.forEachIndexed { index, notificationTime ->
                    DropdownMenuItem(
                        text = { Text(text = intervalStringArray[index]) },
                        onClick = {
                            onValueChange(notificationTime)
                            expanded = false
                        },
                    )
                }
            }
        }
    }
    State Management

    The MainViewModel uses a complex combination of flows and state updates. Review the logic carefully to ensure correctness and efficiency. Consider simplifying the state management if possible.

        private val alarmManager: IAlarmManager,
        userDataRepository: UserDataRepository,
    ) : ViewModel() {
    
        private val _dateTimeState = MutableStateFlow(DateDialogUiData())
        val dateTimeState = _dateTimeState.asStateFlow()
        private lateinit var currentDateTime: LocalDateTime
        private lateinit var today: LocalDateTime
        private val timeListDefault = mutableListOf(
            LocalTime(7, 0, 0),
            LocalTime(13, 0, 0),
            LocalTime(19, 0, 0),
            LocalTime(20, 0, 0),
            LocalTime(20, 0, 0),
        )
    
        @OptIn(ExperimentalMaterial3Api::class)
        var datePicker: DatePickerState = DatePickerState(
            initialSelectedDateMillis = System.currentTimeMillis(),
            locale = Locale.getDefault(),
        )
    
        @OptIn(ExperimentalMaterial3Api::class)
        var timePicker: TimePickerState = TimePickerState(12, 4, is24Hour = false)
        private lateinit var currentLocalDate: LocalDate
    
        private val setOfSelected = MutableStateFlow<Set<Long>>(setOf())
        private val currentNotepads = userDataRepository
            .userData
            .mapLatest { it.mainData }
            .flatMapLatest {
                notepadRepository.getNotePadsWithMainData(it)
            }
        val mainState = combine(
            currentNotepads,
            userDataRepository.userData.mapLatest { it.mainData },
            setOfSelected,
        ) { notepad, mainData, setOfSelected ->
    
            MainState.Success(
                notePads = notepad,
                mainData = mainData,
                setOfSelected = setOfSelected,
            )
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(),
            initialValue = MainState.Loading,
        )
    
        val searchQuery = TextFieldState()
        private val searchTriple = MutableStateFlow<
            Triple<
                List<SearchSort.Type>,
                List<SearchSort.Color>,
                List<SearchSort.Label>,
                >,
            >(Triple(emptyList(), emptyList(), emptyList()))
        private val searchSort = MutableStateFlow<SearchSort?>(null)
        private var isTextAfterSearchSort = false
    
        val searchState = combine(
            snapshotFlow { searchQuery.text }
                .debounce(200),
            notepadRepository.getNotePads(),
            searchTriple,
            searchSort,
    
        ) { query, notepads, triple, searchSort ->
            val old = SearchState.Success(
                searches = notepads,
                types = triple.first,
                color = triple.second,
                label = triple.third,
                searchSort = searchSort,
            )
    
            val searchList = onSearch(old)
    
            SearchState.Success(
                searches = searchList,
                types = triple.first,
                color = triple.second,
                label = triple.third,
                searchSort = searchSort,
            )
        }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = SearchState.Loading,
            )
    
        private fun onSearch(mainState: SearchState.Success): List<NotePad> {
            return when {
                mainState.searchSort != null -> {
                    var list = when (val searchSort = mainState.searchSort) {
                        is SearchSort.Color -> {
                            mainState.searches.filter { it.color == searchSort.colorIndex }
                        }
    
                        is SearchSort.Label -> {
                            mainState.searches.filter { it.labels.any { it.id == searchSort.id } }
                        }
    
                        is SearchSort.Type -> {
                            when (searchSort.index) {
                                0 -> mainState.searches.filter { it.reminder > 0 }
                                1 -> mainState.searches.filter { it.isCheck }
                                2 -> mainState.searches.filter { it.images.isNotEmpty() }
                                3 -> mainState.searches.filter { it.voices.isNotEmpty() }
                                4 -> mainState.searches.filter { it.images.any { it.isDrawing } }
                                5 -> mainState.searches.filter { it.uris.isNotEmpty() }
                                else -> mainState.searches
                            }
                        }
    
                        null -> TODO()
                    }
    
                    if (searchQuery.text.isNotBlank()) {
                        isTextAfterSearchSort = true
    
                        list = list.filter {
                            it.toString().contains(
                                searchQuery.text,
                                true,
                            )
                        }
                    }
    
                    if (isTextAfterSearchSort && searchQuery.text.isBlank()) {
                        isTextAfterSearchSort = false
                        onSetSearch(null)
                    }
    
                    list
                }
    
                searchQuery.text.isNotBlank() -> {
                    val list = mainState.searches.filter {
                        it.toString().contains(searchQuery.text, true)
                    }
    
                    list
                }
    
                else -> emptyList()
            }
        }
    UI Complexity

    The NewTopMainAppBar composable is very large and combines multiple UI elements. Consider separating the search bar and top app bar into distinct composables for better organization and reusability.

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun MainTopAppBar(
        isGrid: Boolean = false,
        navigateToSearch: () -> Unit = {},
        onNavigate: () -> Unit = {},
        scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
        onToggleGrid: () -> Unit = {},
    ) {
        TopAppBar(
            modifier = Modifier,
            title = {
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier
                        .clickable { navigateToSearch() }
                        .fillMaxWidth()
                        .padding(4.dp)
                        .padding(end = 16.dp)
                        .clip(
                            RoundedCornerShape(
                                topEnd = 50f,
                                topStart = 50f,
                                bottomEnd = 50f,
                                bottomStart = 50f,
                            ),
                        )
                        .background(MaterialTheme.colorScheme.secondaryContainer),
                ) {
                    IconButton(onClick = onNavigate) {
                        Icon(
                            imageVector = NoteIcon.Menu,
                            contentDescription = "menu",
                        )
                    }
                    Text(
                        modifier = Modifier.weight(1f),
                        text = stringResource(Rd.string.modules_designsystem_search_note),
                        style = MaterialTheme.typography.titleMedium,
                        textAlign = TextAlign.Center,
                    )
                    IconButton(onClick = { onToggleGrid() }) {
                        if (!isGrid) {
                            Icon(imageVector = NoteIcon.GridView, contentDescription = "grid")
                        } else {
                            Icon(imageVector = NoteIcon.ViewAgenda, contentDescription = "column")
                        }
                    }
                }
            },
            scrollBehavior = scrollBehavior,
            colors = TopAppBarDefaults.topAppBarColors(
                containerColor = Color.Transparent,
                scrolledContainerColor = Color.Transparent,
            ),
        )
    }
    
    @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
    @Composable
    fun NewTopMainAppBar(
        modifier: Modifier = Modifier,
        sharedTransitionScope: SharedTransitionScope,
        animatedContentScope: AnimatedVisibilityScope,
        searchQuery: TextFieldState = rememberTextFieldState(),
        searchState: SearchState = SearchState.Loading,
        isGrid: Boolean = false,
        scrollBehavior: SearchBarScrollBehavior? = null,
        onNavigateIcon: () -> Unit = {},
        onToggleGrid: () -> Unit = {},
        onSetSearch: (SearchSort?) -> Unit = {},
        onNoteClick: (Long) -> Unit = {},
        onExpandSearch: (Boolean) -> Unit = {},
    
    ) {
        val searchBarState = rememberSearchBarState(initialValue = SearchBarValue.Collapsed)
        val scope = rememberCoroutineScope()
    
        LaunchedEffect(key1 = searchBarState.currentValue) {
            onExpandSearch(searchBarState.currentValue == SearchBarValue.Expanded)
        }
    
        val inputField =
            @Composable {
                SearchBarDefaults.InputField(
                    modifier = Modifier,
                    searchBarState = searchBarState,
                    textFieldState = searchQuery,
                    onSearch = { scope.launch { searchBarState.animateToCollapsed() } },
                    placeholder = { Text(stringResource(Rd.string.modules_designsystem_search_note)) },
                    leadingIcon = {
                        if (searchBarState.currentValue == SearchBarValue.Expanded) {
                            IconButton(
                                onClick = { scope.launch { searchBarState.animateToCollapsed() } },
                            ) {
                                Icon(NoteIcon.ArrowBack, contentDescription = "Back")
                            }
                        } else {
                            IconButton(
                                onClick = onNavigateIcon,
                            ) {
                                Icon(NoteIcon.Menu, contentDescription = "menu")
                            }
                        }
                    },
                    trailingIcon = {
                        if (searchBarState.currentValue == SearchBarValue.Expanded) {
                            IconButton(
                                onClick = { searchQuery.clearText() },
                            ) {
                                Icon(NoteIcon.Clear, contentDescription = "clear")
                            }
                        } else {
                            IconButton(onClick = { onToggleGrid() }) {
                                if (!isGrid) {
                                    Icon(imageVector = NoteIcon.GridView, contentDescription = "grid")
                                } else {
                                    Icon(
                                        imageVector = NoteIcon.ViewAgenda,
                                        contentDescription = "column",
                                    )
                                }
                            }
                        }
                    },
                )
            }
    
        TopSearchBar(
            modifier = modifier,
            state = searchBarState,
            inputField = inputField,
            scrollBehavior = scrollBehavior,
        )
        ExpandedFullScreenSearchBar(
            state = searchBarState,
            inputField = inputField,
        ) {
            when (searchState) {
                is SearchState.Loading -> {
                    Box(
                        Modifier.fillMaxSize(),
                        contentAlignment = Alignment.Center,
                    ) {
                        NoteLoadingWheel("")
                    }
                }
    
                is SearchState.Success -> {
                    LazyVerticalStaggeredGrid(
                        modifier = Modifier
                            .padding(16.dp),
                        columns = StaggeredGridCells.Fixed(if (isGrid) 2 else 1),
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
                        verticalItemSpacing = 8.dp,
    
                    ) {
                        if (searchQuery.text.isNotBlank() && searchState.searches.isEmpty()) {
                            item(span = StaggeredGridItemSpan.FullLine) {
                                Column(
                                    Modifier.fillMaxSize(),
                                    horizontalAlignment = Alignment.CenterHorizontally,
                                    verticalArrangement = Arrangement.Center,
                                ) {
                                    Icon(imageVector = NoteIcon.Search, contentDescription = "search")
                                    Text(text = stringResource(Rd.string.modules_designsystem_no_result))
                                }
                            }
                        }
                        if (searchState.searches.isEmpty() && searchQuery.text.isBlank()) {
                            if (searchState.types.isNotEmpty()) {
                                item(span = StaggeredGridItemSpan.FullLine) {
                                    LabelBox(
                                        title = stringResource(Rd.string.modules_designsystem_types),
                                        searchState.types,
                                        onItemClick = onSetSearch,
                                    )
                                }
                            }
    
                            if (searchState.label.isNotEmpty()) {
                                item(span = StaggeredGridItemSpan.FullLine) {
                                    LabelBox(
                                        title = stringResource(Rd.string.modules_designsystem_labels),
                                        searchState.label,
                                        onItemClick = onSetSearch,
                                    )
                                }
                            }
                            if (searchState.color.isNotEmpty()) {
                                item(span = StaggeredGridItemSpan.FullLine) {
                                    Text(text = stringResource(Rd.string.modules_designsystem_colors))
                                }
    
                                item(span = StaggeredGridItemSpan.FullLine) {
                                    FlowRow(
                                        verticalArrangement = Arrangement.spacedBy(4.dp),
    
                                        horizontalArrangement = Arrangement.spacedBy(4.dp),
                                    ) {
                                        searchState.color.forEach { color ->
                                            Surface(
                                                onClick = {
                                                    onSetSearch(color)
                                                },
                                                shape = CircleShape,
                                                color = if (color.colorIndex == -1) Color.White else NoteIcon.noteColors[color.colorIndex],
                                                modifier = Modifier
                                                    .width(40.dp)
                                                    .aspectRatio(1f),
    
                                            ) {
                                                if (color.colorIndex == -1) {
                                                    Icon(
                                                        imageVector = NoteIcon.FormatColorReset,
                                                        contentDescription = "done",
                                                        tint = Color.Gray,
                                                        modifier = Modifier.padding(4.dp),
                                                    )
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
    
                        noteItems(
                            modifier = Modifier,
                            sharedTransitionScope = sharedTransitionScope,
                            animatedContentScope = animatedContentScope,
                            items = searchState.searches,
                            onNoteClick = onNoteClick,
                            onSelectedCard = {},
                            setOfSelected = emptySet(),
                            sharedName = "search",
                        )
                    }
                }
            }
    
    //        SearchResults(
    //            onResultClick = {
    //                result - & gt;
    //                textFieldState.setTextAndPlaceCursorAtEnd(result)
    //                scope.launch { searchBarState.animateToCollapsed() }
    //            }
    //        )
        }

    Copy link
    Contributor

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Handle null userData safely

    The code handles the case where uiState is not MainActivityUiState.Success by
    providing a default MainData(). However, this might lead to unexpected behavior if
    userData is null. Add a null check for userData to prevent potential
    NullPointerExceptions. Consider providing a more informative default value or
    handling the null case explicitly.

    app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt [104-105]

    -currentMainArg = (uiState as? MainActivityUiState.Success)
    -    ?.userData?.mainData ?: MainData(),
    +currentMainArg = (uiState as? MainActivityUiState.Success)?.userData?.mainData ?: MainData(NoteType.NOTE),
    Suggestion importance[1-10]: 8

    __

    Why: The suggestion addresses a potential NullPointerException by explicitly handling the case where userData might be null. Adding a null check and providing a default value improves the code's robustness and prevents unexpected crashes.

    Medium
    Add missing dialog content

    The NotificationDialogNew function lacks a crucial text parameter in the AlertDialog
    composable. This parameter is essential for displaying content within the dialog.
    Add a text parameter to display relevant information to the user. Consider using a
    NotificationContent composable to manage the dialog's body.

    modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteficationDialog.kt [85-86]

     +    @SuppressLint("NewApi")
     +    @OptIn(ExperimentalMaterial3Api::class)
     +    @Composable
     +    fun NotificationDialogNew(
     +        notificationUiState: NotificationUiState,
     +        isEdit: Boolean = false,
     +        showDialog: Boolean = false,
     +        onDismissRequest: () -> Unit = {},
     +        onSetAlarm: () -> Unit = { },
     +        onDeleteAlarm: () -> Unit = {},
     +
     +    ) {
     +        var showDateDialog by remember {
     +            mutableStateOf(false)
     +        }
     +        var showTimeDialog by remember {
     +            mutableStateOf(false)
     +        }
     +        var isError by remember {
     +            mutableStateOf(false)
     +        }
     +
     +        if (showDialog) {
     +            AlertDialog(
     +                onDismissRequest = onDismissRequest,
     +                title = { Text(text = if (isEdit) "Edit Reminder" else "Add Reminder") },
    -+                text = {
    -+                },
    ++                text = { NotificationContent(notificationUiState = notificationUiState) },
     +                confirmButton = {
     +                    Button(
     +                        onClick = {
     +                            onSetAlarm()
     +                            onDismissRequest()
     +                        },
     +                        enabled = !isError,
     +                    ) {
     +                        Text(text = "Save")
     +                    }
     +                },
     +                dismissButton = {
     +                    Row {
     +                        if (isEdit) {
     +                            TextButton(
     +                                onClick = {
     +                                    onDismissRequest()
     +                                    onDeleteAlarm()
     +                                },
     +                            ) {
     +                                Text(text = "Delete")
     +                            }
     +                            Spacer(modifier = Modifier.width(8.dp))
     +                        }
    -+                    TextButton(onClick = { onDismissRequest() }) {
    -+                        Text(text = "Cancel")
    -+                    }
    -+                }
    -+            },
    -+        )
    -+        if (showDateDialog) {
    -+            val dateState = rememberDatePickerState()
    -+
    -+            DatePickerDialog(
    -+                onDismissRequest = {
    -+                    showDateDialog = false
    -+                },
    -+                confirmButton = {
    -+                    Button(
    -+                        onClick = {
    -+                            showDateDialog = false
    -+                        },
    -+                    ) {
    -+                        Text(text = "Set date")
    ++                        TextButton(onClick = { onDismissRequest() }) {
    ++                            Text(text = "Cancel")
    ++                        }
     +                    }
     +                },
    -+                dismissButton = {
    -+                    TextButton(
    -+                        onClick = {
    -+                            showDateDialog = false
    -+                        },
    -+                    ) {
    -+                        Text(text = "Cancel")
    -+                    }
    -+                },
    -+            ) {
    -+                DatePicker(
    -+                    state = dateState,
    -+                    //   dateValidator = { it > (System.currentTimeMillis() - (48 * 60 * 60 * 1000)) }
    -+                )
    ++            )
    ++            if (showDateDialog) {
    ++                val dateState = rememberDatePickerState()
    ++
    ++                DatePickerDialog(
    ++                    onDismissRequest = {
    ++                        showDateDialog = false
    ++                    },
    ++                    confirmButton = {
    ++                        Button(
    ++                            onClick = {
    ++                                showDateDialog = false
    ++                            },
    ++                        ) {
    ++                            Text(text = "Set date")
    ++                        }
    ++                    },
    ++                    dismissButton = {
    ++                        TextButton(
    ++                            onClick = {
    ++                                showDateDialog = false
    ++                            },
    ++                        ) {
    ++                            Text(text = "Cancel")
    ++                        }
    ++                    },
    ++                ) {
    ++                    DatePicker(
    ++                        state = dateState,
    ++                        //   dateValidator = { it > (System.currentTimeMillis() - (48 * 60 * 60 * 1000)) }
    ++                    )
    ++                }
     +            }
    -+        }
    -+        if (showTimeDialog) {
    -+            val timeState = rememberTimePickerState()
    -+            DatePickerDialog(
    -+                onDismissRequest = onDismissRequest,
    -+                confirmButton = {
    -+                    Button(
    -+                        onClick = {
    -+                            showTimeDialog = false
    -+                        },
    -+                    ) {
    -+                        Text(text = "Set time")
    -+                    }
    -+                },
    -+                dismissButton = {
    -+                    TextButton(
    -+                        onClick = {
    -+                            showTimeDialog = false
    -+                        },
    -+                    ) {
    -+                        Text(text = "Cancel")
    -+                    }
    -+                },
    -+            ) {
    -+                TimePicker(state = timeState)
    ++            if (showTimeDialog) {
    ++                val timeState = rememberTimePickerState()
    ++                DatePickerDialog(
    ++                    onDismissRequest = onDismissRequest,
    ++                    confirmButton = {
    ++                        Button(
    ++                            onClick = {
    ++                                showTimeDialog = false
    ++                            },
    ++                        ) {
    ++                            Text(text = "Set time")
    ++                        }
    ++                    },
    ++                    dismissButton = {
    ++                        TextButton(
    ++                            onClick = {
    ++                                showTimeDialog = false
    ++                            },
    ++                        ) {
    ++                            Text(text = "Cancel")
    ++                        }
    ++                    },
    ++                ) {
    ++                    TimePicker(state = timeState)
    ++                }
     +            }
     +        }
     +    }
    ++
    ++@Composable
    ++private fun NotificationContent(
    ++    modifier: Modifier = Modifier,
    ++    notificationUiState: NotificationUiState,
    ++) {
    ++    // Add your content here
     +}
    Suggestion importance[1-10]: 7

    __

    Why: The suggestion correctly points out a missing text parameter in the AlertDialog. Adding a text parameter with relevant content significantly improves the user experience. The use of a separate NotificationContent composable for managing the dialog body is a good practice for maintainability.

    Medium
    General
    Refactor MainData to use enum

    The MainData class uses a Long to represent different note types. This is less
    readable and maintainable than using an enum. Consider refactoring MainData to
    directly use the NoteType enum, removing the need for the when statement. This
    improves code clarity and reduces potential errors from incorrect index values.

    modules/model/src/main/java/com/mshdabiola/model/MainData.kt [3-11]

    -data class MainData(val index: Long = NoteType.NOTE.index) {
    -    val noteType: NoteType
    -        get() = when (index) {
    -            -1L -> NoteType.NOTE
    -            -2L -> NoteType.ARCHIVE
    -            -3L -> NoteType.TRASH
    -            -4L -> NoteType.REMAINDER
    -            else -> NoteType.LABEL
    -        }
    -}
    +data class MainData(val noteType: NoteType = NoteType.NOTE)
    Suggestion importance[1-10]: 7

    __

    Why: The suggestion improves code readability and maintainability by replacing the less readable Long-based approach with a more direct use of the NoteType enum. This simplifies the code and reduces the risk of errors from incorrect index values.

    Medium

    This commit refactors the `NotificationDialogNew` and its related composables for improved functionality and state management.
    
    - **`NoteficationDialog.kt`**:
        - Removed the separate `NotificationContent` composable and integrated its logic directly into `NotificationDialogNew`.
        - Modified `NotificationDialogNew` to manage `pagerState` and `isError` locally.
        - Updated `TimeTextDropbox` and `DateTextDropbox` to use `LocalDateTime.time` and `LocalDateTime.date` from `notificationUiState.currentDateTime` respectively.
        - `TimeTextDropbox`:
            - Added an `onErrorMessage` lambda to propagate error state.
            - `times` list is now initialized within the composable using `remember`.
            - Added `showTimeDialog` state to manage the visibility of `TimePicker` within a `DatePickerDialog`.
            - `onValueChange` now directly provides `LocalTime`.
            - Removed the `NotificationTime` data class and replaced its usages with `LocalTime` or a sealed interface for `PickTime`.
        - `DateTextDropbox`:
            - `dates` list is now initialized within the composable using `remember`.
            - Added `showDateDialog` state to manage the visibility of `DatePicker` within a `DatePickerDialog`.
            - `onValueChange` now directly provides `LocalDate`.
            - Removed the `NotificationDate` data class and replaced its usages with `LocalDate` or a sealed interface for `PickDate`.
        - `IntervalTextDropbox`:
            - `notificationIntervals` list is now initialized within the composable using `remember`.
        - `NotificationPlace`:
            - `places` list is now initialized within the composable using `remember`.
            - Added an optional `editState` parameter of type `TextFieldState`.
        - Updated previews for `NotificationDialogNewPreview`, `TimeTextDropboxPreview`, `DateTextDropboxPreview`, and `IntervalTextDropboxPreview` to reflect the changes.
        - Imported `android.os.Build`, `kotlinx.datetime.DateTimeUnit`, `kotlinx.datetime.LocalDateTime`, `kotlinx.datetime.toKotlinLocalDate`, `androidx.compose.foundation.text.input.TextFieldState`, and `androidx.compose.material3.getSelectedDate`.
        - Removed `NotificationContentPreview`.
        - The `NotificationUiState` in previews now uses `currentDateTime` instead of separate `currentTime` and `currentDate`.
    This commit refactors the data models related to notification UI state.
    
    - In `NotificationUiState.kt`:
        - Replaced `currentTime: LocalTime` and `currentDate: LocalDate` with `currentDateTime: LocalDateTime`.
        - Removed `times: List<NotificationTime>`, `dates: List<NotificationDate>`, `intervals: List<NotificationInterval>`, and `places: List<NotificationPlace>` as these are no longer directly part of the UI state but derived or handled differently.
    - In `NotificationTime.kt`:
        - Changed `NotificationTime` from a data class to a sealed class with two subtypes:
            - `Time(val localTime: LocalTime)`: Represents a specific time.
            - `PickTime`: Represents an action to pick a time.
    - In `NotificationDate.kt`:
        - Changed `NotificationDate` from a data class to a sealed class with two subtypes:
            - `Date(val localDate: LocalDate)`: Represents a specific date.
            - `PickDate`: Represents an action to pick a date.
    This commit refactors the `NotificationUiState` and `DateTimeRepository` to simplify the management of notification times, dates, and places.
    
    **Key changes:**
    
    -   **`DateTimeRepository.kt`**:
        -   Updated the initialization of `times` to use `NotificationTime.Time` and `NotificationTime.PickTime` instead of manually setting `isEnable`.
        -   Updated the initialization of `dates` to use `NotificationDate.Date` and `NotificationDate.PickDate` instead of manually setting `isEnable`.
        -   Removed `setPlace`, `setDate`, `setTime`, and `setInterval` functions. The state is now managed directly by the ViewModel or UI layer.
        -   Removed the logic that initialized `_notificationUiState.value` with default current time, date, interval, and place, along with the lists of times, dates, intervals, and places. This initialization is now expected to be handled by the consumer of the repository or a ViewModel.
    …fication state management
    
    This commit updates the `NotificationDialogNew` composable and `MainViewModel` to improve notification state handling.
    
    - In `feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt`:
        - The `NotificationDialogNew` composable now accepts a `notificationUiState` parameter.
        - Removed `isError`, `pair`, and `onSetValue` parameters from `NotificationDialogNew` as they are now managed within `notificationUiState`.
    - In `feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt`:
        - Added a `notificationUiState` property of type `NotificationUiState`. This state holder includes `currentDateTime`, `currentInterval`, and `currentPlace` for notifications.
        - Initialized `notificationUiState` with sample data.
    This commit introduces several improvements to the `MainScreen` UI and its corresponding `MainViewModel`.
    
    **UI Enhancements (`MainScreen.kt`, `TopbarAndDialog.kt`):**
    -   The `TopAppBar` now uses `pinnedScrollBehavior` when items are selected, providing a more stable UI during selection mode.
    -   The `TopAppBar` background color changes to `secondaryContainer` when items are selected.
    -   The `labelName` in `MainState.Success` is now nullable.
    -   The title of the `TopAppBar` dynamically updates to show the label name when `NoteType.LABEL` is active and no items are selected.
    -   `LazyVerticalStaggeredGrid` now uses `rememberLazyStaggeredGridState` for better state management.
    -   Improved padding for `LazyVerticalStaggeredGrid` and section titles ("Pin", "Other").
    -   Added default modifier to `MainScreen`.
    -   Passed modifier to `LoadingState`.
    
    **ViewModel Logic (`MainViewModel.kt`):**
    -   Refactored `mainState` flow to combine individual data sources (`currentNotepads`, `label`, `noteDisplayCategory`, `selectedNotesState`, `isGrid`) for better clarity and efficiency.
    -   Renamed `onClearSelection()` to `deselectNotes()` for better clarity.
    -   `deselectNotes()` is now called when:
        -   The last selected item is deselected.
        -   Actions like pinning, changing color, archiving, trashing, or making a copy are performed.
        -   A note is sent.
    -   When a single selected note is clicked again, it now deselects all notes instead of just removing that one from the selection.
    
    **Navigation (`MainNavigation.kt`):**
    -   Updated `onClearSelection` call in `MainScreen` to use the new `mainViewModel::deselectNotes`.
    
    **State (`MainState.kt`):**
    -   Changed `labelName` in `MainState.Success` to be nullable (`String?`).
    This commit introduces new icons and string resources to support functionalities related to the trash, such as restoring notes, unarchiving, and permanently deleting items.
    
    **Key additions:**
    
    -   **`modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt`**:
        -   Added `NoteIcon.RestoreFromTrash` using `Icons.Outlined.Restore`.
    -   **`modules/designsystem/src/main/res/values/modules_designsystem_strings.xml`**:
        -   Added string `modules_designsystem_unarchive` ("Unarchive").
        -   Added string `modules_designsystem_dialog_empty_trash` ("Empty Trash?").
        -   Added string `modules_designsystem_dialog_empty_trash_content` ("All note in Trash will be permanently deleted.").
        -   Added string `modules_designsystem_dialog_delete_forever` ("Delete Forever").
        -   Added string `modules_designsystem_dialog_delete_forever_content` ("This note will be permanently deleted.").
    -   **`modules/designsystem/src/main/res/values/strings.xml`**:
        -   Added string `modules_designsystem_delete_forever` ("Delete forever").
    This commit updates the filtering logic in `NotePadRepository` for notes displayed under a specific label.
    
    - **`modules/data/src/main/kotlin/com/mshdabiola/data/repository/NotePadRepository.kt`**:
        - When filtering notes for a `NoteDisplayCategory.Category`, the initial filter now excludes notes that are in `TRASH` or `ARCHIVE`. Previously, it only included notes with `NoteType.LABEL`.
    This commit updates the `TopbarAndDialog.kt` to provide contextual actions based on the current `noteDisplayCategory.noteType`.
    
    When in the `TRASH` view and notes are selected:
    -   A "Restore from Trash" icon button is shown.
    -   The "More" dropdown menu offers a "Delete forever" option.
    
    When in other views (including `ARCHIVE`) and notes are selected:
    -   The "Archive" menu item in the "More" dropdown will now dynamically display "Unarchive" if the current view is `ARCHIVE`, and "Archive" otherwise.
    -   The existing actions (Pin, Notification, Color, Label, Delete, Copy, Send) remain available.
    
    **Key changes:**
    
    -   **`feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt`**:
        -   Added `onDeleteForever` and `onRestore` parameters to the `MainTopBar` composable.
        -   Modified the `actions` composable:
            -   Wrapped the existing actions logic in a `when (noteDisplayCategory.noteType)` block.
            -   **`NoteType.TRASH` case:**
                -   Added an `IconButton` for `onRestore` using `NoteIcon.RestoreFromTrash`.
                -   The "More" `DropdownMenu` now contains a "Delete forever" `DropdownMenuItem` triggering `onDeleteForever`.
            -   **`else` case (covers `NOTE`, `ARCHIVE`, `REMINDER`, `LABEL`):**
                -   The `DropdownMenuItem` for archiving/unarchiving now checks `noteDisplayCategory.noteType == NoteType.ARCHIVE` to display "Unarchive" or "Archive" text accordingly.
                -   Retains the Pin, Notification, Color, Label, Delete, Copy (if single selection), and Send (if single selection) actions.
    …lity
    
    This commit introduces functionality to toggle archive status, permanently delete notes, and restore notes from trash in the `MainViewModel`.
    
    - **`feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt`**:
        - `onArchiveNote()`:
            - Now toggles the `noteType` between `NoteType.ARCHIVE` and `NoteType.NOTE` for selected notes.
        - `onMoveToTrash()`:
            - When moving notes to trash, `isPin` is now set to `false`.
        - `onDeleteForever()` (New method):
            - Retrieves selected notes.
            - Deselects notes.
            - Calls `notepadRepository.delete()` to permanently remove the selected notes.
        - `onRestore()` (New method):
            - Retrieves selected notes.
            - Maps selected notes to `NoteType.NOTE`.
            - Deselects notes.
            - Calls `notepadRepository.upsert()` to restore the notes.
    This commit introduces two new dialog components to `MainScreen.kt` for handling note deletion in the trash:
    
    - **`EmptyTrashDialog`**:
        - Displays a confirmation dialog when the user attempts to empty the trash.
        - Provides "Close" and "Delete" actions.
        - Shown/hidden based on a `show` boolean parameter.
    - **`DeleteForeverDialog`**:
        - Displays a confirmation dialog when the user attempts to permanently delete selected notes from the trash.
        - Provides "Close" and "Delete" actions.
        - Shown/hidden based on a `show` boolean parameter.
    
    **Key changes:**
    
    - **`feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt`**:
        - Added `onDeletedForever` and `onRestore` lambda parameters to the `MainScreen` composable.
        - Passed `onDeleteForever` and `onRestore` to the `MainTopAppBar` composable.
        - Implemented `EmptyTrashDialog` composable:
            - Takes `show`, `onDismissRequest`, and `onDelete` parameters.
            - Uses `AlertDialog` with appropriate title, text, and buttons.
        - Implemented `DeleteForeverDialog` composable:
            - Takes `show`, `onDismissRequest`, and `onDelete` parameters.
            - Uses `AlertDialog` with appropriate title, text, and buttons.
        - Both dialogs use `AnimatedVisibility` for appearance.
    This commit introduces confirmation dialogs for "Delete Forever" and "Empty Trash" actions in the main feature.
    
    - **`feature/main/src/main/kotlin/com/mshdabiola/main/navigation/MainNavigation.kt`**:
        - Added `showDeleteForever` and `showEmptyTrash` mutable states to control dialog visibility.
        - The `onDeleteAllTrash` callback in `MainScreen` now sets `showEmptyTrash = true`.
        - The `onDeletedForever` callback in `MainScreen` now sets `showDeleteForever = true`.
        - Added `DeleteForeverDialog` composable, displayed when `showDeleteForever` is true. Its `onDelete` action calls `mainViewModel::onDeleteForever`.
        - Added `EmptyTrashDialog` composable, displayed when `showEmptyTrash` is true. Its `onDelete` action calls `mainViewModel::onDeleteAllTrash`.
        - Imported `DeleteForeverDialog` and `EmptyTrashDialog`.
    This commit integrates the new search feature into the main application navigation.
    
    - **`app/src/main/java/com/mshdabiola/playnotepad/navigation/NoteNavHost.kt`**:
        - The `mainScreen` composable now correctly uses `navController::navigateToSearch` for its `navigateToSearch` parameter.
        - Added the `search` composable destination to the `NavHost`, enabling navigation to the search screen. It receives `sharedTransitionScope` and an `onBack` lambda.
    This commit updates the placeholder text for the search functionality.
    
    - **`modules/designsystem/src/main/res/values/modules_designsystem_strings.xml`**:
        - Changed the `modules_designsystem_search_note` string from "Search note" to "Search your note".
    This commit renames `MainScreen` to `SearchScreen` and `SearchhhhhhhhScreen` to `SearchScreen` for consistency across the search feature. It also updates navigation logic and simplifies the UI components within the search screen.
    
    **Key changes:**
    
    *   **`feature/search/src/androidTest/kotlin/com/mshdabiola/search/SearchScreenTest.kt`**:
        *   Updated UI test documentation and calls from `MainScreen` to `SearchhhhhhhhScreen`.
    *   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchScreen.kt`**:
        *   Renamed `MainScreen` composable to `SearchScreen`.
        *   Removed `LaunchedEffect` that was previously used to monitor `searchBarState.currentValue`.
        *   Simplified the `leadingIcon` in the `SearchBarDefaults.InputField`. It now consistently shows an `ArrowBack` icon and triggers the `onBack` callback. Previously, it showed a `Menu` icon or `ArrowBack` based on the search bar state.
        *   Simplified the `trailingIcon`. It now consistently shows a `Clear` icon for clearing the search query. Previously, it could show a grid/agenda toggle icon.
        *   Removed unused parameters: `onNavigateIcon`, `onToggleGrid`, `onNoteClick`, `onExpandSearch`.
    *   **`feature/search/src/main/kotlin/com/mshdabiola/search/navigation/SearchNavigation.kt`**:
        *   Renamed `navigateToMain` to `navigateToSearch`.
        *   Renamed `MainRoute` to `SearchRoute`.
        *   Renamed `mainScreen` graph builder function to `search`.
        *   Updated the `search` graph builder to call the renamed `SearchScreen` composable.
        *   Adjusted parameters for the `search` graph builder to `onBack`. Removed `onShowSnack`, `navigateToDetail`, `navigateToSelectLevel`, and `onOpenDrawer`.
    *   **`feature/search/src/screenshotTest/kotlin/com/mshdabiola/search/SearchScreenScreenshotTests.kt`**:
        *   Updated screenshot test function `MainScreenShot` to call `SearchhhhhhhhScreen`.
        *   Renamed `mainState` parameter to `searchState` in the `SearchhhhhhhhScreen` call.
    This commit enhances the TopBar in the main feature with a dynamic search bar and display mode toggle.
    
    - **`feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt`**:
        - Introduced a display mode toggle button (grid/list view) to the TopBar actions.
        - When `noteDisplayCategory.noteType` is `NoteType.NOTE`, the TopBar title now displays an `OutlinedCard` that acts as a search bar, prompting the user to "Search note". The title is centered in this case.
        - For other `noteType`s, the TopBar title continues to display the `labelName`, aligned to the start.
        - Added a `@Preview` Composable `MainTopBarPreview` to showcase the `MainTopBar` with default NOTE type.
        - Removed an unused `else -> {}` block in the actions section.
    This commit removes the `modifier` parameter from the `search` composable function call within `NoteNavHost.kt` as it was unused.
    
    - **`app/src/main/java/com/mshdabiola/playnotepad/navigation/NoteNavHost.kt`**:
        - The `modifier` argument has been removed from the `search(...)` call.
    This commit simplifies the `SearchViewModel` by removing the `searchTriple` and `searchSort` `MutableStateFlow`s, along with the logic associated with them.
    
    The `searchState` now combines only the `searchQuery` and the full list of `notepads` from the repository. It temporarily always emits `SearchState.Loading`. The complex search and filtering logic (`onSearch`, `onExpandSearch`) has been removed. The `onSetSearch` function is now a no-op.
    
    **Key changes:**
    
    -   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchViewModel.kt`**:
        -   Removed `searchTriple` (MutableStateFlow for search sort categories).
        -   Removed `searchSort` (MutableStateFlow for the current search sort).
        -   Removed `isTextAfterSearchSort` boolean flag.
        -   Simplified the `searchState` flow:
            -   It no longer uses `searchTriple` or `searchSort` in its `combine` block.
            -   It now directly emits `SearchState.Loading` based on `query` and `notepads`.
        -   Removed the `onSearch` private function which contained the core filtering logic.
        -   Removed the `onExpandSearch` function which was responsible for populating search filter options.
        -   The `onSetSearch` function now has an empty body.
        -   Removed unused imports: `com.mshdabiola.model.NotePad`, `kotlinx.coroutines.flow.MutableStateFlow`, `kotlinx.coroutines.flow.first`, `kotlinx.coroutines.flow.update`, and `kotlinx.coroutines.launch`.
    This commit refactors the `SearchState` sealed class by introducing a new `Select` state.
    
    - **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchState.kt`**:
        - Added a new data class `Select` to `SearchState`. This state will hold lists of `SearchSort.Type`, `SearchSort.Color`, and `SearchSort.Label`.
        - Moved `types`, `color`, and `label` properties from the `Success` state to the new `Select` state.
        - Added an `isGrid` boolean property to the `Success` state.
    This commit significantly refactors the `SearchScreen` composable, moving away from `ExpandedFullScreenSearchBar` to a `Scaffold` with a custom `TopAppBar` and `TextField` for search input. It also introduces a new `SearchState.Select` to handle the display of search filter options (types, labels, colors) when the search query is blank.
    
    **Key changes:**
    
    *   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchScreen.kt`**:
        *   Replaced `ExpandedFullScreenSearchBar` with a `Scaffold`.
        *   The top app bar now contains a `TextField` for search input and a back button.
            *   `TextField` uses `searchQuery` state, includes a clear button, and is configured for search IME action.
            *   Top app bar has a `MaterialTheme.colorScheme.secondaryContainer` background.
        *   The `SharedTransitionScope` is now a receiver for the `SearchScreen` composable.
        *   Removed `rememberCoroutineScope` as it's no longer needed for search bar state.
        *   Introduced `rememberLazyStaggeredGridState` for the results grid and added `TrackScrollJank`.
        *   **State Handling Updates:**
            *   When `searchState` is `SearchState.Loading`, a loading indicator is shown within the `Scaffold`'s content area (respecting padding).
            *   When `searchState` is `SearchState.Success`:
                *   The `LazyVerticalStaggeredGrid` now uses `searchState.isGrid` to determine column count.
                *   Content padding is applied from the `Scaffold`.
                *   Horizontal padding of `16.dp` is added.
            *   **New `SearchState.Select` state:**
                *   When `searchQuery` is blank, this state is active.
                *   Displays `LabelBox` components for "Types", "Labels", and "Colors" in a `Column`.
                *   Each `LabelBox` now handles its specific item type (Type, Label, Color).
        *   **`LabelBox` Composable Refactor:**
            *   Added `space` and `numPerRow` parameters for better layout control.
            *   The "Show more/less" button logic now depends on `numPerRow`.
            *   The rendering of items within `LabelBox` is now conditional based on `SearchSort` subtype:
                *   `SearchSort.Label` and `SearchSort.Type` render as `SearchLabel`.
                *   `SearchSort.Color` renders as a circular `Surface` with the corresponding color or a reset icon for no color.
        *   Added a `@Preview` composable `NewSearchScreenPreview` to demonstrate the `SearchState.Select` UI.
        *   Imported `ExperimentalMaterial3ExpressiveApi`.
        *   Updated `sharedTransitionScope` and `animatedContentScope` parameter order and usage.
    This commit modifies the way the `SearchScreen` composable is invoked within the search feature's navigation.
    
    - **`feature/search/src/main/kotlin/com/mshdabiola/search/navigation/SearchNavigation.kt`**:
        - The `SearchScreen` composable is now called as an extension function on `sharedTransitionScope` (`sharedTransitionScope.SearchScreen(...)`).
        - Consequently, the `sharedTransitionScope` parameter has been removed from the `SearchScreen` call as it's now the receiver.
    This commit introduces a new reusable `NoteCard` composable function.
    
    **Key features of `NoteCard`:**
    
    - Displays a `NotePad` item with its title, details, and checks.
    - Shows images associated with the note, arranged in rows of up to three.
    - Indicates if a note has voice recordings.
    - Displays reminders and labels associated with the note.
    - Supports selection state with a distinct border.
    - Applies background color or image based on `NotePad` properties.
    - Uses `SharedTransitionScope` for potential shared element transitions.
    - Includes a preview (`NoteCardPreview`) for development and testing.
    
    **File added:**
    
    - **`modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteCard.kt`**: Contains the `NoteCard` composable function and its preview.
    This commit introduces search and filtering capabilities to the `SearchViewModel`. It also updates the `SearchScreen` to display search results and selection options.
    
    **Key changes:**
    
    *   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchViewModel.kt`**:
        *   Added logic to fetch notepads based on `noteDisplayCategory` from `UserDataRepository`.
        *   Introduced `searchSort` `MutableStateFlow` to manage the current search filter.
        *   Updated the `searchState` flow to combine `searchQuery`, `notepads`, `searchSort`, and `isGrid` to produce `SearchState`.
            *   If the query is blank and no `searchSort` is applied, it emits `SearchState.Select` with available filter options (types, labels, colors).
            *   Otherwise, it emits `SearchState.Success` with filtered notes.
        *   Added `onBlankSearch` function to generate the filter options for `SearchState.Select`.
        *   Added `onSearch` function to filter notepads based on the search query and active `searchSort`. This function handles filtering by:
            *   `SearchSort.Color`: Filters by note color.
            *   `SearchSort.Label`: Filters by note labels.
            *   `SearchSort.Type`: Filters by note characteristics (reminder, checklist, has images, has voice, has drawings, has URIs).
            *   It also supports text search in conjunction with the selected `SearchSort`.
        *   The `onSetSearch` function is now used to update the `searchSort` state.
    *   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchScreen.kt`**:
        *   Added an `onNoteClick` parameter to handle note item clicks.
        *   In the `LazyVerticalStaggeredGrid` for `SearchState.Success`:
            *   Added `items(items = searchState.searches, key = { it.id })` to display the filtered `NoteCard`s.
        *   Updated preview data for `SearchState.Select` to reflect new `SearchSort.Type` and `SearchSort.Label` indices.
    This commit removes the `NoteCard` composable from `feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt` and modifies `MainScreen.kt` to use `com.mshdabiola.ui.NoteCard` directly within its `LazyVerticalStaggeredGrid`.
    
    The `noteItems` extension function has been removed from `MainScreen.kt` as its functionality is now inlined.
    
    **Key changes:**
    
    -   **`feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt`**:
        -   `MainScreen` composable now directly receives `SharedTransitionScope` as its first parameter. The `sharedTransitionScope` parameter has been removed.
        -   Import for `NoteCard` (local) removed, and `com.mshdabiola.ui.NoteCard` is imported and used.
        -   Import for `LazyStaggeredGridScope` removed.
        -   Import for `kotlinx.collections.immutable.toImmutableList` removed.
        -   Import for `com.mshdabiola.analytics.LocalAnalyticsHelper` removed.
        -   The `noteItems` extension function and its usages have been replaced by directly calling `items` on `LazyVerticalStaggeredGrid` and instantiating `com.mshdabiola.ui.NoteCard` for each pinned and unpinned note.
        -   The `sharedBounds` modifier is no longer applied directly in `MainScreen` as `com.mshdabiola.ui.NoteCard` likely handles its own shared element transitions.
        -   Added `@SuppressLint("UnusedSharedTransitionModifierParameter")` to the `MainScreenPreview` composable.
        -   In `MainScreenPreview`, the `sharedTransitionScope` argument is removed from the `MainScreen` call.
    -   **`feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt`**:
        -   Removed the `NoteCard` composable function and its associated preview (`NoteCardPreview`).
        -   Removed numerous unused imports related to `NoteCard`'s previous implementation (e.g., `BorderStroke`, `Image`, `combinedClickable`, layout components, `Color`, `ContentScale`, `Coil3`, `FlowLayout2`, `LabelCard`, `ReminderCard`).
    This commit introduces several changes to the Search feature, including UI adjustments, ViewModel logic updates, and state management refinements.
    
    **Key changes:**
    
    -   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchViewModel.kt`**:
        -   Removed unused `SavedStateHandle` and `IAlarmManager` dependencies.
        -   The `searchState` flow now initializes with the result of `onBlankSearch(emptyList())` instead of `SearchState.Loading`.
        -   Added `onSetSearch` function to update the `searchSort` value.
        -   In `onBlankSearch`:
            -   `backgrounds` are now derived from `it.color` instead of `it.background`.
            -   `backgrounds` are now sorted after being distinct.
    -   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchScreen.kt`**:
        -   The `searchState` parameter now defaults to `SearchState.Select()`.
        -   The clear button in the `BasicTextField` now clears the search query and calls `onSetSearch(null)`.
        -   The `TopAppBar` container color is now `MaterialTheme.colorScheme.surfaceContainerHigh`.
        -   Removed the `SearchState.Loading` state handling, meaning the loading wheel is no longer displayed.
        -   Adjusted padding for `LazyVerticalStaggeredGrid` and `Column` in `SearchState.Success` and `SearchState.Select` respectively.
        -   The "No result" message is now displayed directly within the `SearchState.Success` block when `searchQuery` is not blank and `searches` is empty, instead of being a separate `item` in the grid.
    -   **`feature/search/src/main/kotlin/com/mshdabiola/search/SearchState.kt`**:
        -   Removed the `SearchState.Loading` data object.
    This commit updates the `MainScreen` composable to be called as an extension function on `SharedTransitionScope` and changes the property used for color indexing in `MainViewModel`.
    
    - **`feature/main/src/main/kotlin/com/mshdabiola/main/navigation/MainNavigation.kt`**:
        - `MainScreen` is now invoked as `sharedTransitionScope.MainScreen(...)` instead of passing `sharedTransitionScope` as a parameter.
    - **`feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt`**:
        - When a single note is selected, `colorIndex` is now assigned from `note.color` instead of `note.background`.
    This commit updates the `searchScreen` composable to enable navigation to the note detail screen.
    
    - **`feature/search/src/main/kotlin/com/mshdabiola/search/navigation/SearchNavigation.kt`**:
        - The `searchScreen` composable function now accepts a `navigateToDetail: (Long) -> Unit` parameter.
        - This `navigateToDetail` function is passed to the `SearchScreen` composable as the `onNoteClick` callback.
    This commit updates the `NoteNavHost` to pass the `navigateToDetail` lambda to the `SearchScreen`.
    
    - **`app/src/main/java/com/mshdabiola/playnotepad/navigation/NoteNavHost.kt`**:
        - The `searchScreen` composable now receives `navigateToDetail = { navController.navigateToDetail(DetailArg(it)) }`.
    This commit refactors the `DetailScreen.kt` by replacing the `DetailRoute` and `EditScreen` composables with a single `SharedTransitionScope.EditScreen` composable function.
    
    The new `EditScreen` now directly takes `SharedTransitionScope` as a receiver and `AnimatedVisibilityScope` as a parameter.
    Several unused imports were removed, and the preview function was updated to use the new `EditScreen` structure, including `AnimatedVisibility` and `SharedTransitionScope` for accurate previewing.
    
    **Key changes:**
    
    - **`feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt`**:
        - Removed `DetailRoute` composable.
        - Renamed and refactored the existing `EditScreen` to be an extension function of `SharedTransitionScope` and accept `AnimatedVisibilityScope`.
        - Removed parameters like `onShowSnackbar`, `editViewModel`, and distinct `sharedTransitionScope` from the main composable, as ViewModel logic is expected to be handled by the caller or a higher-level route.
        - Updated `EditScreenPreview` to correctly invoke the new `EditScreen` composable within a `SharedTransitionScope` and `AnimatedVisibility` block, providing sample `NotePad` data.
        - Added `@SuppressLint("UnusedSharedTransitionModifierParameter")` to `EditScreenPreview`.
        - Removed unused imports:
            - `android.Manifest`
            - `android.content.pm.PackageManager`
            - `android.os.Build`
            - `androidx.activity.compose.rememberLauncherForActivityResult`
            - `androidx.activity.result.contract.ActivityResultContracts`
            - `androidx.compose.runtime.rememberCoroutineScope`
            - `androidx.core.app.ShareCompat`
            - `androidx.core.content.FileProvider`
            - `androidx.hilt.navigation.compose.hiltViewModel`
            - `androidx.lifecycle.compose.collectAsStateWithLifecycle`
            - `com.mshdabiola.ui.FirebaseScreenLog`
            - `com.mshdabiola.ui.NotificationDialogNew`
            - `com.mshdabiola.ui.supportVoice`
            - `java.io.File`
        - The `sharedBounds` modifier in `EditScreen` now references `note_${notepad.id}` for the `sharedContentState` key.
        - Added an `id` parameter to `EditScreen` with a default value of `0L`, though it's not currently used in the composable's logic.
        - Updated `EditScreenPreview` to provide a full `NotePad` object and `TextFieldState` for title and content.
    This commit refactors `DetailState` from a sealed class hierarchy (`Loading`, `Success`, `Error`) to a single data class.
    
    - **`feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailState.kt`**:
        - Removed `DetailState.Loading`, `DetailState.Success`, and `DetailState.Error` sealed class variants.
        - `DetailState` is now a single data class containing:
            - `notePad: NotePad` (defaulting to a new `NotePad()`)
            - `title: TextFieldState` (defaulting to a new `TextFieldState()`)
            - `detail: TextFieldState` (defaulting to a new `TextFieldState()`)
        - Removed the `getSuccess` extension function as it's no longer applicable.
    This commit significantly refactors the `DetailViewModel` to improve state management, data persistence, and overall structure. It moves away from multiple `MutableStateFlow`s for individual note properties and `TextFieldState`s towards a consolidated `detailState` Flow that combines data from the repository and local UI state.
    
    **Key changes:**
    
    *   **State Management:**
        *   Removed individual `MutableStateFlow`s for `note`, `title`, and `content`.
        *   Removed `_state` and `state` `MutableStateFlow`s.
        *   Introduced `currentNoteId: MutableStateFlow<Long>` to track the ID of the note being edited.
        *   Introduced `currentNote: Flow<NotePad?>` which listens to `currentNoteId` and fetches the corresponding `NotePad` from `notePadRepository`.
        *   Created `initState: DetailState` to hold the initial/current UI state, including `TextFieldState` for title and detail.
        *   The primary state is now exposed via `detailState: StateFlow<DetailState>`, which is a combination of:
            *   Debounced snapshot flows for `initState.title.text` and `initState.detail.text`.
            *   The `currentNote` flow.
        *   The `detailState` flow handles:
            *   Initializing `initState.title` and `initState.detail` with data from `currentNote` when it's first loaded.
            *   Saving a new note if `currentNote` is null (e.g., for a new note).
            *   Automatically saving changes to the `NotePad` (title and detail) when the debounced text fields differ from the `currentNote`.
            *   Updating the `detailState.notePad` with the latest version from `currentNote`.
    *   **Data Persistence:**
        *   Removed the `saveNote()` function. Saving is now handled reactively within the `detailState` flow or by explicitly calling the new private `save(NotePad)` function.
        *   The `save(NotePad)` function is now the central point for calling `notePadRepository.upsert()`.
        *   All functions that modify the `NotePad` (e.g., `onCheckChange`, `addCheck`, `pinNote`, `onColorChange`, etc.) now:
            *   Get the current `NotePad` using `getNotePad()` (which reads from `detailState.value.notePad`).
            *   Create a modified copy of the `NotePad`.
            *   Call `save()` with the modified `NotePad`.
    *   **Initialization and New Notes:**
        *   Removed the explicit `init` block logic that fetched the initial note and set up `TextFieldState`s. This is now handled within the `detailState` flow.
        *   Added `savNewNote()` function to create and persist a new `NotePad` when `currentNote` is null, and then updates `currentNoteId`.
    *   **Checklist Logic:**
        *   When converting text to checkboxes (`changeToCheckBoxes`), the `initState.detail` is now cleared.
        *   When hiding checkboxes (`hideCheckBoxes`), the combined content of the checkboxes is appended to `initState.detail`.
    *   **Voice Notes & Image Handling:**
        *   Saving voice notes (`saveVoice`) now appends recognized text to `initState.detail`.
    *   **Removed Unused Code:**
        *   Removed `TextFieldState` import as it's now part of `DetailState`.
        *   Removed unused `first()` import from kotlinx.coroutines.flow.
        *   Simplified imports.
    *   **Other:**
        *   The `id` property was renamed to `currentNoteId`.
        *   `title` and `content` `TextFieldState`s are now part of `DetailState` (accessed via `initState.title` and `initState.detail` within the ViewModel).
        *   Debounce time for title and content updates is set to 200ms and 500ms respectively.
    This commit significantly refactors the `detailScreen` composable within `feature/detail/src/main/kotlin/com/mshdabiola/detail/navigation/DetailNavigation.kt`. It introduces several BottomSheet composables for various actions, integrates a `DetailViewModel` for state management, and handles permission requests for notifications.
    
    **Key changes:**
    
    -   **ViewModel Integration and State Management**:
        -   A `DetailViewModel` is now `hiltViewModel()` and its `detailState` is collected as `State` using `collectAsStateWithLifecycle`.
        -   Local `MutableState` variables (`showModalState`, `noteModalState`, `noteficationModalState`, `colorModalState`, `showDialog`) are introduced to manage the visibility of different UI elements.
    -   **Screen Composition**:
        -   `DetailRoute` has been replaced with `sharedTransitionScope.EditScreen`.
        -   `EditScreen` now receives numerous callbacks from the `DetailViewModel` to handle user interactions like checking items, adding items, playing/pausing voice, pinning notes, archiving, etc.
        -   It also handles navigation to other screens like `navigateToSelectLevel`, `navigateToGallery`, and `navigateToDrawing`.
    -   **BottomSheet Implementation**:
        -   `AddBottomSheet2`: For adding content like images, voice notes, drawings, or converting to checkboxes.
        -   `NoteOptionBottomSheet`: For actions like adding labels, deleting, copying, and sending the note.
        -   `ColorAndImageBottomSheet`: For changing the note's color or background image.
        -   `NotificationBottomSheet`: For setting alarms and managing notifications.
    -   **Notification Handling**:
        -   A `rememberLauncherForActivityResult` is used to request the `POST_NOTIFICATIONS` permission on Android Tiramisu (API 33) and above.
        -   `NotificationDialogNew` is displayed based on the `showDialog` state.
    -   **Sharing Functionality**:
        -   A `send` lambda is implemented to share the note content (title, detail, and images) using `ShareCompat.IntentBuilder`.
        -   `FileProvider` is used to get content URIs for images.
    -   **Navigation**:
        -   The `id` is now extracted from `it.savedStateHandle.toRoute<DetailArg>().id`.
        -   `navigateToDrawing` now accepts the note ID and drawing ID.
    -   **UI Logging**:
        -   `FirebaseScreenLog(screen = "edit_screen")` is added.
    -   **Other**:
        -   The `navigateToDetail` function signature remains largely the same.
        -   `supportVoice()` is used to conditionally enable voice-related features in `AddBottomSheet2`.
    Copy link

    @gemini-code-assist gemini-code-assist bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Code Review

    The pull request refactors the notification dialog, updates MainData to a data class, improves the main screen UI, and updates repositories and view models to handle the new MainData type. The changes look good overall, with some minor suggestions for improved code readability.

    @mshdabiola
    Copy link
    Owner Author

    @gemini-code-assist how to improve the code

    Copy link

    Hi @mshdabiola,

    Thanks for reaching out! To improve the code in this pull request, I recommend focusing on the key areas identified in the initial review:

    • Code Complexity: The NotificationDialogNew function and its related composables are quite extensive. Breaking them down into smaller, more focused functions will significantly improve readability and maintainability.
    • State Management: The MainViewModel uses a complex combination of flows and state updates. Simplifying this logic where possible will enhance correctness and efficiency.
    • UI Complexity: The NewTopMainAppBar composable is very large. Separating the search bar and top app bar into distinct composables would lead to better organization and reusability.

    In addition to these high-level points, please review the specific code suggestions provided earlier in the PR comments. Addressing these will offer concrete improvements:

    • Handle null userData safely: This prevents potential NullPointerExceptions and makes the code more robust.
    • Add missing dialog content: Ensure NotificationDialogNew displays relevant information to the user.
    • Refactor MainData to use enum: This improves code clarity and reduces potential errors.

    Addressing these points will lead to a more maintainable, readable, and robust codebase. Let me know if you have any specific questions as you work through these improvements!

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    1 participant