Skip to content
This repository was archived by the owner on Dec 21, 2025. It is now read-only.

Conversation

@xanderlmk
Copy link
Owner

@xanderlmk xanderlmk commented Jun 15, 2025

Summary by Sourcery

Enable multi-select and batch deletion of cards in the editing list, refactor UI components and data flows to support selection state, and streamline model, repository, and navigation logic.

New Features:

  • Allow long-press to enter selection mode and tap to toggle multi-card selection in the editing list.
  • Provide batch actions (select all, clear selection, delete selected cards) via a new toolbar menu in the navigation drawer and card list.
  • Disable search and keyboard interactions when in selection mode to prevent conflicts.

Enhancements:

  • Extract common button and menu patterns into reusable composables under uiFunctions.buttons.
  • Migrate model.uiModels to a unified model.ui package and update imports project-wide.
  • Introduce selectedCards and searchQuery StateFlows in repository and view models to drive UI state.
  • Simplify CardSelector and card item rendering by consolidating question extraction with toQuestion() extension and selection icons.
  • Add deleteCardList transaction in the DAO and handle mapping exceptions in stream flows.

Documentation:

  • Add Spanish and English strings for batch delete dialog, search placeholder, and keyboard disabled state.
  • Update deck_content.html CSS to improve text wrapping, width constraints, and layout styling.

Chores:

  • Clean up redundant imports, inline lambdas, and formatting across multiple classes.

@sourcery-ai
Copy link

sourcery-ai bot commented Jun 15, 2025

Reviewer's Guide

This PR adds multi-select and bulk delete capabilities to the edit-cards screen using new composables and repository flows, centralizes common button components into a shared package, renames the model.uiModels package to model.ui, and simplifies CardTypeRepository and DAO APIs by exposing new Flow- and suspend-based methods.

Sequence Diagram for Entering Selection Mode and Selecting Cards

sequenceDiagram
    actor User
    participant ECL as EditCardsList
    participant ECLVM as EditingCardListViewModel
    participant NVM as NavViewModel
    participant OCTR as OfflineCardTypeRepository

    User->>+ECL: Long-presses CardItem
    ECL->>+ECLVM: toggleCard(ct)
    ECLVM->>+OCTR: toggleCard(ct)
    OCTR-->>-ECLVM: 
    ECLVM-->>-ECL: 
    ECL->>+NVM: isSelectingTrue()
    NVM-->>-ECL: 
    ECL-->>-User: UI updates (selection mode active, card selected)

    User->>+ECL: Taps another CardItem
    ECL->>+ECLVM: toggleCard(ct_other)
    ECLVM->>+OCTR: toggleCard(ct_other)
    OCTR-->>-ECLVM: 
    ECLVM-->>-ECL: 
    ECL-->>-User: UI updates (card selection toggled)
Loading

Sequence Diagram for Bulk Deleting Selected Cards

sequenceDiagram
    actor User
    participant CLO as CardListOptions
    participant NVM as NavViewModel
    participant OCTR as OfflineCardTypeRepository
    participant CTD as CardTypesDao

    User->>+CLO: Clicks "Delete" option (in menu)
    CLO->>CLO: Shows DeleteCards dialog
    User->>+CLO: Confirms deletion
    CLO->>+NVM: deleteCardList()
    NVM->>+OCTR: deleteCTs()
    OCTR->>+CTD: deleteCardList(selectedCards)
    CTD-->>-OCTR: 
    OCTR-->>-NVM: Returns success/failure
    NVM-->>-CLO: 
    CLO-->>-User: UI updates (cards deleted, dialog dismissed)
Loading

Class Diagram: Core Logic for Multi-Select and Bulk Delete

classDiagram
    direction LR
    class NavViewModel {
        +isSelecting: StateFlow~Boolean~
        +isSelectingTrue()
        +selectAll()
        +clearSelection()
        +resetSelection()
        +deleteCardList(): Boolean
        +resetSearchQuery()
    }
    class EditingCardListViewModel {
        +selectedCards: StateFlow~List~CT~~
        +searchQuery: StateFlow~String~
        +toggleCard(CT)
    }
    class OfflineCardTypeRepository {
        +selectedCards: StateFlow~List~CT~~
        +searchQuery: StateFlow~String~
        +toggleCard(CT)
        +toggleAllCards(Int)
        +clearSelection()
        +updateQuery(String)
        +resetQuery()
        +deleteCTs()
        +getAllCardTypesStream(deckId: Int): Flow~List~CT~~
        +getAllCardTypes(deckId: Int): List~CT~
    }
    class CardTypeRepository {
        <<Interface>>
        +selectedCards: StateFlow~List~CT~~
        +searchQuery: StateFlow~String~
        +toggleCard(CT)
        +toggleAllCards(Int)
        +clearSelection()
        +updateQuery(String)
        +resetQuery()
        +deleteCTs()
        +getAllCardTypesStream(deckId: Int): Flow~List~CT~~
        +getAllCardTypes(deckId: Int): List~CT~
    }
    class CardTypesDao {
        <<DAO>>
        +deleteCardList(List~CT~)
        +getAllCardTypesStream(deckId: Int): Flow~List~AllCardTypes~~
        +getAllCardTypes(deckId: Int): List~AllCardTypes~
    }

    NavViewModel ..> CardTypeRepository : Uses
    EditingCardListViewModel ..> CardTypeRepository : Uses
    OfflineCardTypeRepository ..|> CardTypeRepository
    OfflineCardTypeRepository ..> CardTypesDao : Uses
Loading

File-Level Changes

Change Details Files
Add multi-select and bulk-delete support in the card-editing list
  • Extended EditingCardsList with CardItem composable handling tap/long-press gestures to toggle selection and enter selection mode
  • Enhanced ActionsIconButton and created CardListOptions in uiFunctions.buttons for select all, clear selection, and delete dialog
  • Updated NavViewModel and CardTypeRepository (plus Offline implementation) to track selectedCards, manage queries, and expose deleteCardList flow
EditingCardsList.kt
CardOptionsButtons.kt (uiFunctions/buttons)
NavViewModel.kt
OfflineCardTypeRepository.kt
CardTypesDao.kt
Centralize and standardize UI button components
  • Introduced uiFunctions.buttons package with reusable BackButton, SubmitButton, CancelButton, etc.
  • Replaced direct Material Button usages across multiple views and navigation actions with the new centralized components
  • Updated all relevant imports to point to uiFunctions.buttons
Buttons.kt
CardOptionsButtons.kt
various view and navigation files
Rename model.uiModels package to model.ui
  • Moved all uiModels classes into model.ui and updated their package declarations
  • Adjusted imports in viewmodels, views, and repositories to reference model.ui instead of model.uiModels
  • Removed legacy uiModels references
Fields.kt
PreferencesManager.kt
other model/ui related files
Streamline CardTypeRepository and DAO interfaces
  • Augmented CardTypeRepository with selectedCards StateFlow, toggleCard, clearSelection, toggleAllCards, searchQuery, updateQuery, resetQuery, and deleteCTs
  • Refactored OfflineCardTypeRepository to implement the new flows and suspend methods
  • Modified CardTypesDao to add getAllCardTypesStream vs suspend getAllCardTypes, and a transactional deleteCardList method
CardTypeRepository.kt
OfflineCardTypeRepository.kt
CardTypesDao.kt
Refactor EditCardsList and CardSelector UI
  • Extracted CardItem composable and replaced Button list items with Box+detectTapGestures
  • Unified CardSelector to handle selection state, show icons for selected/unselected, and disabled search when in selection mode
  • Cleaned up list filtering using toQuestion() extension and simplified snapshotFlow handling
EditingCardsList.kt
CardSelector.kt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@xanderlmk xanderlmk merged commit ab3712e into main Jun 15, 2025
1 check passed
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @xanderlmk - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `app/src/main/java/com/belmontCrest/cardCrafter/views/cardViews/editCardViews/EditingCardsList.kt:105` </location>
<code_context>
-                        CardSelector(filtered, index)
-                    }
+                items(filtered.size, key = { index -> filtered[index].getCardId() }) { index ->
+                    val selected = selectedCTL.any { it.getCardId() == filtered[index].getCardId() }
+
+                    CardItem(
</code_context>

<issue_to_address>
Potential performance issue with linear search for selection.

Using selectedCTL.any for each item results in O(n) lookup per item. Switching to a Set of selected IDs would improve lookup to O(1).
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
                items(filtered.size, key = { index -> filtered[index].getCardId() }) { index ->
                    val selected = selectedCTL.any { it.getCardId() == filtered[index].getCardId() }

                    CardItem(
=======
                val selectedCardIds = selectedCTL.map { it.getCardId() }.toSet()
                items(filtered.size, key = { index -> filtered[index].getCardId() }) { index ->
                    val selected = filtered[index].getCardId() in selectedCardIds

                    CardItem(
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `app/src/main/java/com/belmontCrest/cardCrafter/localDatabase/dbInterface/repositories/OfflineCardTypeRepository.kt:51` </location>
<code_context>
+        }
+    }
+
+    override suspend fun toggleAllCards(deckId: Int) {
+        _selectedCards.update {
+            val cts = getAllCardTypes(deckId)
</code_context>

<issue_to_address>
toggleAllCards does not handle deselecting all when all are already selected.

Consider implementing a toggle-all/deselect-all feature for improved UX, so users can also deselect all cards when all are selected.
</issue_to_address>

### Comment 3
<location> `app/src/main/java/com/belmontCrest/cardCrafter/navigation/NavViewModel.kt:158` </location>
<code_context>
+        viewModelScope.launch { flashCardRepository.deleteCard(card) }
+    }
+
+    suspend fun deleteCardList(): Boolean {
+        return withContext(Dispatchers.IO) {
+            try {
</code_context>

<issue_to_address>
deleteCardList swallows exceptions and only logs them.

Consider propagating the exception or providing user feedback instead of just logging the error and returning false.

Suggested implementation:

```
    suspend fun deleteCardList() {
        withContext(Dispatchers.IO) {
            cardTypeRepository.deleteCTs()
        }
    }

```

- Update all usages of `deleteCardList()` to handle exceptions and provide user feedback as appropriate, since it no longer returns a Boolean or swallows exceptions.
- If you want to provide user feedback, catch the exception at the call site and display an error message to the user.
</issue_to_address>

### Comment 4
<location> `app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/buttons/Buttons.kt:59` </location>
<code_context>
-}
-
-@Composable
-fun SmallAddButton(
-    onClick: () -> Unit, iconSize: Int = 45,
-    getUIStyle: GetUIStyle, modifier: Modifier
</code_context>

<issue_to_address>
Use Dp type for iconSize instead of Int

Changing the parameter to `Dp` allows callers to pass dp values directly and removes the need for internal `.dp` conversions.

Suggested implementation:

```
fun SmallAddButton(
    onClick: () -> Unit, iconSize: Dp = 45.dp,
    getUIStyle: GetUIStyle, modifier: Modifier
) {

```

If there are any usages of `iconSize.dp` within the body of `SmallAddButton`, remove the `.dp` as `iconSize` is now already a `Dp`. Also, update all call sites of `SmallAddButton` to pass a `Dp` value for `iconSize` if they specify it.
</issue_to_address>

### Comment 5
<location> `app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/buttons/Buttons.kt:52` </location>
<code_context>
-    ) {
-        Icon(
-            painter = painterResource(R.drawable.merge),
-            contentDescription = "Merge deck",
-        )
-        Text("Merge deck")
</code_context>

<issue_to_address>
Hardcoded contentDescription string

Use a string resource for the contentDescription to support accessibility and localization.

Suggested implementation:

```
        Icon(
            painter = painterResource(R.drawable.merge),
            contentDescription = stringResource(R.string.merge_deck),
        )

```

You also need to add the following to your `res/values/strings.xml` file:

```xml
<string name="merge_deck">Merge deck</string>
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +104 to +107
items(filtered.size, key = { index -> filtered[index].getCardId() }) { index ->
val selected = selectedCTL.any { it.getCardId() == filtered[index].getCardId() }

CardItem(
Copy link

Choose a reason for hiding this comment

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

suggestion (performance): Potential performance issue with linear search for selection.

Using selectedCTL.any for each item results in O(n) lookup per item. Switching to a Set of selected IDs would improve lookup to O(1).

Suggested change
items(filtered.size, key = { index -> filtered[index].getCardId() }) { index ->
val selected = selectedCTL.any { it.getCardId() == filtered[index].getCardId() }
CardItem(
val selectedCardIds = selectedCTL.map { it.getCardId() }.toSet()
items(filtered.size, key = { index -> filtered[index].getCardId() }) { index ->
val selected = filtered[index].getCardId() in selectedCardIds
CardItem(

viewModelScope.launch { flashCardRepository.deleteCard(card) }
}

suspend fun deleteCardList(): Boolean {
Copy link

Choose a reason for hiding this comment

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

suggestion: deleteCardList swallows exceptions and only logs them.

Consider propagating the exception or providing user feedback instead of just logging the error and returning false.

Suggested implementation:

    suspend fun deleteCardList() {
        withContext(Dispatchers.IO) {
            cardTypeRepository.deleteCTs()
        }
    }

  • Update all usages of deleteCardList() to handle exceptions and provide user feedback as appropriate, since it no longer returns a Boolean or swallows exceptions.
  • If you want to provide user feedback, catch the exception at the call site and display an error message to the user.

}

@Composable
fun SmallAddButton(
Copy link

Choose a reason for hiding this comment

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

suggestion: Use Dp type for iconSize instead of Int

Changing the parameter to Dp allows callers to pass dp values directly and removes the need for internal .dp conversions.

Suggested implementation:

fun SmallAddButton(
    onClick: () -> Unit, iconSize: Dp = 45.dp,
    getUIStyle: GetUIStyle, modifier: Modifier
) {

If there are any usages of iconSize.dp within the body of SmallAddButton, remove the .dp as iconSize is now already a Dp. Also, update all call sites of SmallAddButton to pass a Dp value for iconSize if they specify it.

) {
Icon(
painter = painterResource(R.drawable.merge),
contentDescription = "Merge deck",
Copy link

Choose a reason for hiding this comment

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

suggestion: Hardcoded contentDescription string

Use a string resource for the contentDescription to support accessibility and localization.

Suggested implementation:

        Icon(
            painter = painterResource(R.drawable.merge),
            contentDescription = stringResource(R.string.merge_deck),
        )

You also need to add the following to your res/values/strings.xml file:

<string name="merge_deck">Merge deck</string>

@xanderlmk xanderlmk deleted the katex-menu branch July 28, 2025 21:05
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants