Skip to content
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

feat(native-app): inbox bulk selection #17927

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

thoreyjona
Copy link
Contributor

@thoreyjona thoreyjona commented Feb 11, 2025

What

Add bulk selection to inbox documents.

  • Select documents button in top right corner
  • When clicking on select button another button Select all appears in the top left corner
  • Icons change to circles and if the user clicks on any list item it gets selected
  • If the user has at least one item selected an action bar appears at the bottom of the screen showing three actions Star, Archive and Mark as read.
  • If the action returns success for the selected items we display a toast with a success message and update the cache for the documents (did not manage to get the updating of the cache work for archiving documents so I refresh the data in that case)
  • By clicking the icon in the list we can take a short cut into the "select state" (skipping clicking the top right button)
  • Added back the star for starred items in the list but we only show it now if the document is starred, not if it is not and it is not a button like it was before
Screen.Recording.2025-02-11.at.13.39.24.mov

Screenshots / Gifs

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • Formatting passes locally with my changes
  • I have rebased against main before asking for a review

Summary by CodeRabbit

  • New Features
    • Introduced bulk selection in the inbox, enabling users to select individual or all messages, with options to deselect and cancel selections.
    • Added an interactive action bar that allows performing bulk actions such as starring, archiving, and marking messages as read.
    • Implemented toast notifications to provide immediate feedback on the status of bulk actions.
    • Enhanced the visual presentation of message cards and list items to clearly indicate selection and bookmarking statuses.

@thoreyjona thoreyjona requested a review from a team as a code owner February 11, 2025 20:48
Copy link
Contributor

coderabbitai bot commented Feb 11, 2025

Walkthrough

The pull request implements bulk selection functionality for the inbox. It adds new localization keys in the language files (en.ts and is.ts) to support bulk actions such as selecting, deselecting, starring, archiving, and marking documents as read, along with corresponding success and error messages. New React components, including an ActionBar and a Toast, are introduced to facilitate these bulk operations. Additionally, updates to the InboxScreen and related UI components ensure that selection state management and bulk action triggers are integrated with navigation events and user feedback.

Changes

File(s) Change Summary
apps/native/app/src/messages/en.ts
apps/native/app/src/messages/is.ts
Added new key-value pairs supporting bulk select functionality including labels and messages for actions: select, select all, deselect all, cancel, star, archive, mark as read (both success and error feedback).
apps/native/app/src/screens/inbox/components/action-bar.tsx Introduced a new ActionBar component that provides buttons for bulk actions (star, archive, mark as read) with responsive design, animations, and a loading state.
apps/native/app/src/screens/inbox/components/toast.tsx Added a new Toast component for displaying customizable notifications with variant-based styling, auto-dismiss functionality, and entrance/exit animations.
apps/native/app/src/screens/inbox/inbox-filter.tsx Updated import statements to remove duplicate hook imports and added the useNavigationButtonPress hook to handle navigation button presses for clearing filters.
apps/native/app/src/screens/inbox/inbox.tsx Enhanced the InboxScreen to support bulk item selection by updating the PressableListItem props, managing selection state with new state variables, integrating the ActionBar and Toast components, and handling bulk actions via navigation button presses.
apps/native/app/src/ui/lib/card/inbox-card.tsx Updated InboxCardProps and the component to include properties for bookmarking, selectable state, and selection indicator, and made the onPressIcon function optional.
apps/native/app/src/ui/lib/list/list-item.tsx Modified the ListItem component by adding new styled components and props to support selectable and selected states, including visual indicators (checkmark and star icons) based on item status and interactivity.
apps/native/app/src/utils/component-registry.ts Introduced four new button registry entries (InboxBulkSelectButton, InboxBulkSelectAllButton, InboxBulkDeselectAllButton, and InboxBulkSelectCancelButton) to enable bulk selection actions within the inbox.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant S as InboxScreen
    participant AB as ActionBar
    participant T as Toast

    U->>S: Initiates bulk selection (select/deselect items)
    S->>AB: Renders ActionBar with bulk action options
    U->>AB: Presses a bulk action button (e.g., Archive)
    AB->>S: Triggers corresponding bulk action callback
    S->>T: Displays success or error toast notification
Loading

Possibly related PRs

Suggested labels

automerge, deploy-feature

Suggested reviewers

  • thorkellmani
  • snaerseljan

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8b194c and b9605ba.

📒 Files selected for processing (1)
  • apps/native/app/src/screens/inbox/components/toast.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/native/app/src/screens/inbox/components/toast.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tests (native-app)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

nx-cloud bot commented Feb 11, 2025

View your CI Pipeline Execution ↗ for commit b9605ba.

Command Status Duration Result
nx run-many --target=lint --projects=native-app... ✅ Succeeded 7s View ↗
nx run-many --projects native-app --target test... ✅ Succeeded 3s View ↗
nx run-many --target=codegen/frontend-client --... ✅ Succeeded 12s View ↗
nx run-many --target=codegen/backend-schema --a... ✅ Succeeded 21s View ↗

☁️ Nx Cloud last updated this comment at 2025-02-12 08:57:27 UTC

}: {
onClickStar: () => void
onClickArchive: () => void
onClickMarkAsRead: () => void
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we want to make this a reusable component we can remove these actions from it and extract it, I asked Daði and he did not think we would use it in another places. I wanted to try to add as little code I could to the inbox.tsx file since it is already huge so I took this approach ☺️

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
apps/native/app/src/screens/inbox/inbox.tsx (1)

847-953: Bulk actions block is functionally correct; consider refactoring to reduce repetition.
Each of the three actions (star, archive, mark as read) repeats a similar sequence: calling bulkSelectActionMutation, checking success, modifying the cache, resetting select state, and showing a toast. You could extract a helper function to streamline this logic.

+ async function performBulkAction(
+   action: 'bookmark' | 'archive' | 'read',
+   onSuccessTitle: string,
+   onErrorTitle: string,
+ ) {
+   const result = await bulkSelectActionMutation({
+     variables: {
+       input: { action, documentIds: selectedItems },
+     },
+   })
+   if (result.data?.postMailActionV2?.success) {
+     // possibly update cache for bookmark/read
+     if (action !== 'archive') {
+       selectedItems.forEach((id) => {
+         client.cache.modify({
+           id: client.cache.identify({ __typename: 'DocumentV2', id }),
+           fields: {
+             ...(action === 'bookmark' ? { bookmarked: () => true } : {}),
+             ...(action === 'read' ? { opened: () => true } : {}),
+           },
+         })
+       })
+     } else {
+       // archive requires refetch
+       res.refetch()
+     }
+     resetSelectState()
+     showToastForBulkSelectAction({
+       variant: 'success',
+       title: onSuccessTitle,
+     })
+   } else {
+     showToastForBulkSelectAction({
+       variant: 'success',
+       title: onErrorTitle,
+       message: intl.formatMessage({
+         id: 'inbox.bulkSelect.pleaseTryAgain',
+       }),
+     })
+   }
+ }
apps/native/app/src/screens/inbox/components/action-bar.tsx (1)

13-25: Consider using useMemo for style objects.

The style object in the Wrapper component could be memoized to prevent unnecessary recalculations.

Apply this diff to optimize performance:

+const wrapperStyle = (width: number, spacing: number) => 
+  useMemo(() => ({ width: width - spacing }), [width, spacing])

 return (
   <Wrapper
-    style={{ width: screenWidth - theme.spacing[4] }}
+    style={wrapperStyle(screenWidth, theme.spacing[4])}
     entering={FadeInDown}
     exiting={FadeOutDown}
   >
apps/native/app/src/ui/lib/list/list-item.tsx (2)

27-45: Use literal union type for icon states.

The icon states could be more strictly typed to prevent invalid combinations.

Apply this diff to improve type safety:

+type IconState = 
+  | { selectable: true; selected: boolean }
+  | { selectable: false; selected?: never }

-const Icon = styled.View<{
-  unread?: boolean
-  selectable?: boolean
-  selected?: boolean
-}>`
+const Icon = styled.View<IconState & { unread?: boolean }>`

72-79: Consider using a theme token for star icon dimensions.

The hardcoded dimensions for the star icon should be moved to theme tokens for consistency.

Apply this diff:

+// In theme file
+const iconSizes = {
+  small: 16,
+  medium: 24,
+  large: 50,
+}

-  width: 16px;
-  height: 16px;
+  width: ${({ theme }) => theme.iconSizes.small}px;
+  height: ${({ theme }) => theme.iconSizes.small}px;
apps/native/app/src/screens/inbox/inbox-filter.tsx (2)

72-80: Consider using useCallback for clearAllFilters.

The clearAllFilters function should be memoized to prevent unnecessary recreations.

Apply this diff:

-const clearAllFilters = () => {
+const clearAllFilters = useCallback(() => {
   setOpened(false)
   setBookmarked(false)
   setArchived(false)
   setSelectedSenders([])
   setSelectedCategories([])
   setDateFrom(undefined)
   setDateTo(undefined)
-}
+}, [])

82-90: Use useMemo for derived state.

The isSelected calculation should be memoized to prevent unnecessary recalculations.

Apply this diff:

-const isSelected = !!(
+const isSelected = useMemo(() => !!(
   opened ||
   bookmarked ||
   archived ||
   selectedSenders.length ||
   selectedCategories.length ||
   dateFrom ||
   dateTo
-)
+), [opened, bookmarked, archived, selectedSenders, selectedCategories, dateFrom, dateTo])
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e10cb88 and a8b194c.

📒 Files selected for processing (9)
  • apps/native/app/src/messages/en.ts (1 hunks)
  • apps/native/app/src/messages/is.ts (1 hunks)
  • apps/native/app/src/screens/inbox/components/action-bar.tsx (1 hunks)
  • apps/native/app/src/screens/inbox/components/toast.tsx (1 hunks)
  • apps/native/app/src/screens/inbox/inbox-filter.tsx (1 hunks)
  • apps/native/app/src/screens/inbox/inbox.tsx (15 hunks)
  • apps/native/app/src/ui/lib/card/inbox-card.tsx (2 hunks)
  • apps/native/app/src/ui/lib/list/list-item.tsx (8 hunks)
  • apps/native/app/src/utils/component-registry.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`apps/**/*`: "Confirm that the code adheres to the following...

apps/**/*: "Confirm that the code adheres to the following:

  • NextJS best practices, including file structure, API routes, and static generation methods.
  • Efficient state management and server-side rendering techniques.
  • Optimal use of TypeScript for component and utility type safety."
  • apps/native/app/src/screens/inbox/inbox-filter.tsx
  • apps/native/app/src/ui/lib/list/list-item.tsx
  • apps/native/app/src/screens/inbox/components/action-bar.tsx
  • apps/native/app/src/utils/component-registry.ts
  • apps/native/app/src/ui/lib/card/inbox-card.tsx
  • apps/native/app/src/screens/inbox/components/toast.tsx
  • apps/native/app/src/screens/inbox/inbox.tsx
  • apps/native/app/src/messages/is.ts
  • apps/native/app/src/messages/en.ts
🔇 Additional comments (27)
apps/native/app/src/screens/inbox/inbox.tsx (16)

18-18: Import usage looks good.
No issues with adding useNavigationButtonPress.


40-40: No immediate concerns with the new import.
The naming (usePostMailActionMutationMutation) likely comes from generated code and seems fine as is.


47-50: Registry imports are consistent.
The imported ButtonRegistry and ComponentRegistry appear well-organized here.


55-56: Newly imported components look correct.
Both ActionBar and Toast are imported appropriately for bulk selection and toast notifications.


241-250: State initialization for toast feedback is appropriate.
Storing toastInfo and toastVisible in state is a clean, straightforward approach.


285-287: Bulk-selection state variables are well-defined.
Declaring selectState and selectedItems ensures clarity for bulk-select logic.


288-296: Conditional rightButtons for the top bar is neatly structured.
This is a good approach to toggling UI based on the selection state.


317-319: Bulk action mutation is set up properly.
This GraphQL mutation hook is a solid foundation for the upcoming bulk actions.


325-326: allDocumentsSelected logic looks safe.
Optional chaining prevents runtime errors; fallback to false if res.data?.documentsV2?.data is undefined.


366-380: Button press handler is well-organized.
All possible button IDs lead to distinct, readable actions. Consider guarding for unexpected button IDs if needed.


394-400: Resets selection when switching tabs.
Good user-experience move to ensure the UI remains consistent once the user leaves the inbox.


401-426: Dynamic leftButtons for Select All / Deselect All
This is a clear UX flow for toggling selection states.


477-493: Toast presentation helper is concise and effective.
It cleanly sets toast data and triggers display.


535-538: Resetting selection state is straightforward.
A single function handling both arrays and boolean resets is maintainable.


676-680: Resetting selection on search focus is a nice touch.
Prevents confusion with partially selected items if the user begins a new query.


696-697: Ensures a clean slate prior to filter navigation.
Resetting state before switching routes is consistent with the rest of the code.

apps/native/app/src/ui/lib/card/inbox-card.tsx (7)

16-18: Properties added for bookmarked and selection states look correct.
These optional fields (bookmarked, selectable, selected) align with the new bulk selection feature.


20-20: Optional icon press handler is a useful addition.
Allows specialized behavior when pressing the icon while preserving compatibility for existing use cases.


26-26: Destructuring onPressIcon.
Combines well with the rest of the props for a flexible card UI.


32-32: Receiving bookmarked prop here is consistent with the store usage.
It’s good that you coerce it to a boolean later for starred.


34-35: Defaulting selectable and selected to false is a safe approach.
Prevents accidental undefined behavior in the card’s logic.


49-49: Mapping bookmarked to starred with a double-bang is straightforward.
Keeps the ListItem interface usage consistent.


51-53: Extension for selection logic is implemented cleanly.
The selectable, selected, and onPressIcon props are passed down effectively to ListItem.

apps/native/app/src/screens/inbox/components/toast.tsx (1)

1-123: Toast component is well-structured.

  1. Using a variant-based config (toastSchemes) centralizes the styling logic.
  2. The useEffect timer to auto-hide is straightforward and keeps user interactions minimal.
  3. Animations (FadeInDown, FadeOutDown) enhance the user experience.
    Overall, the component is clear, modular, and easy to integrate.
apps/native/app/src/utils/component-registry.ts (1)

76-79: LGTM! Consistent button registry entries.

The new bulk selection buttons follow the established naming pattern and maintain consistency with the existing registry entries.

apps/native/app/src/messages/en.ts (1)

240-254: LGTM! The bulk selection translations are well-structured and user-friendly.

The translations for the bulk selection feature are clear, concise, and follow consistent patterns:

  • Action buttons are clearly labeled
  • Success messages confirm the actions
  • Error messages provide clear feedback and next steps
apps/native/app/src/messages/is.ts (1)

240-254: LGTM! The Icelandic translations maintain consistency with the English version.

The translations for the bulk selection feature are well-aligned with their English counterparts while maintaining natural Icelandic language flow:

  • All 14 keys are properly translated
  • The meaning and intent of each message is preserved
  • The translations follow Icelandic language conventions

Comment on lines +72 to +93
<ActionBarItem
onPress={onClickStar}
underlayColor={theme.shade.shade400}
>
<ItemWrapper wrapItem={shouldWrapItems}>
<Image
source={starIcon}
style={{
width: 16,
height: 16,
tintColor: theme.color.blue400,
}}
resizeMode="contain"
/>
<Typography variant="body3">
<FormattedMessage
id="inbox.bulkSelectActionStar"
defaultMessage="Stjörnumerkja"
/>
</Typography>
</ItemWrapper>
</ActionBarItem>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes to action buttons.

The action buttons should have proper accessibility labels to improve the user experience for screen reader users.

Apply this diff to add accessibility attributes:

 <ActionBarItem
   onPress={onClickStar}
   underlayColor={theme.shade.shade400}
+  accessibilityRole="button"
+  accessibilityLabel={intl.formatMessage({
+    id: 'inbox.bulkSelectActionStar',
+    defaultMessage: 'Stjörnumerkja'
+  })}
 >

Apply similar changes to the Archive and Mark as Read buttons.

Also applies to: 94-115, 116-137

})
if (result.data?.postMailActionV2?.success) {
resetSelectState()
res.refetch()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried figuring out how to do this in the cache like for the other two operations but could not make it work despite following the documentation from apollo. I don't have any easy way to vizualise the cache since we upgraded to React Native 0.74 since then Flipper is not supported like it was before. Spent some time trying to set it up manually but no luck. Decided it was not worth the effort for now so refetching the data instead.

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

Successfully merging this pull request may close these issues.

1 participant