1. Introduction
The Gallery module is a cornerstone feature within the Nimbly application ecosystem, meticulously engineered to provide users with a centralized, intuitive, and highly performant interface for viewing, managing, and interacting with a diverse array of media attachments. These attachments are integral to various application workflows, including but not limited to audit findings, site inspection records, user-generated content for issue reporting, and evidence documentation.
The primary objective of the Gallery module is to deliver a seamless and rich user experience for browsing and interacting with images (e.g., JPEG, PNG), videos (e.g., MP4, MOV), and potentially other document types. This involves not only displaying media but also providing robust functionalities such as:
- Advanced Filtering: Allowing users to narrow down media based on criteria like date, attachment type, associated site, department, auditor, or questionnaire.
- Flexible Sorting: Enabling users to arrange media according to different attributes like creation date, file size, or name.
- Efficient Searching: Providing a quick way to find specific attachments using keywords or other identifiers.
- [Bulk Operationss](../Bulk Operation/BulkOperationOverview.md): Supporting actions like multi-select for batch downloading.
- Varied Layouts: Offering different views, such as grids of various thumbnail sizes or list views, and grouping by date attributes (day, month, year) or site.
This document offers an exhaustive technical deep-dive into the Gallery module. It covers its architectural design, the intricacies of its core components, the flow of data, critical utility functions, screen navigation paradigms within the Expo application, interactions with backend APIs, key third-party and internal package dependencies, and detailed explanations of pivotal logical flows. The intention is to equip developers, quality assurance engineers, and product managers with the necessary knowledge to understand, maintain, extend, and troubleshoot this feature effectively, ensuring its continued stability and evolution in line with user needs and technological advancements.
2. Overall Architecture
The Gallery module is designed with a modular and layered architecture to promote separation of concerns, enhance reusability, and improve maintainability. This approach allows for independent development and testing of core business logic and UI components, distinct from their integration into specific application shells like the Expo mobile app.
2.1. Core Architectural Principles
- Modularity: The feature is broken down into smaller, manageable modules (components, controllers, utils) with well-defined responsibilities.
- Reusability: Core UI components and logic are designed to be potentially reusable across different parts of the Nimbly platform or even in different applications with minimal adaptation.
- Scalability: The architecture aims to support a growing number of attachments and increasing complexity of features without significant performance degradation. This is partly achieved through efficient data fetching (pagination, virtualization in lists) and optimized rendering.
- Maintainability: Clear separation of concerns makes it easier to understand, debug, and modify the codebase. Type safety with TypeScript further aids in this.
- Testability: Isolating logic in controllers and custom hooks allows for more focused unit and integration testing. UI components can be tested using storybooks or component testing libraries.
2.2. Key Architectural Layers
The architecture primarily consists of two main layers:
-
Core Feature Package (
packages/app/features/gallery): This is the heart of the Gallery module. It is a self-contained package that encapsulates all the fundamental building blocks required for the gallery’s functionality. This package is intended to be platform-agnostic where possible.- UI Components (
./components/): A comprehensive suite of React components responsible for rendering every aspect of the gallery’s user interface. This includes thumbnail renderers, media viewers, interactive menus, filter selection interfaces, loading indicators, and more. These components are built using@my/ui(Nimbly’s internal UI library) and other relevant UI primitives. - Controllers & State Management (
./controller/): This directory houses the logic for managing the gallery’s state, orchestrating data fetching operations, handling complex user interactions, and processing business rules. It heavily utilizes:- Jotai: For global and local atomic state management (e.g., selected filters, UI modes like select mode, tile size).
- TanStack Query (
@tanstack/react-query): For declarative data fetching, caching, and synchronization with the backend, ensuring data freshness and optimizing API call frequency. - React Hook Form: For managing the state, validation, and submission of filter forms.
- Utility Functions (
./utils/): A collection of helper functions and custom React hooks that provide reusable logic for common tasks. Examples include generating thumbnail URLs, orchestrating bulk downloads, formatting dates, and JWT token retrieval. - Type Definitions (
./type.ts): Centralized TypeScript type definitions and interfaces that model the data structures used throughout the gallery module (e.g., attachment details, filter parameters, API responses). This ensures type safety and improves developer understanding of data contracts.
- UI Components (
-
Application Integration Layer (
apps/expo/app/gallery): This layer is specific to the Nimbly Expo mobile application. It is responsible for integrating the core gallery features into the mobile app’s navigation and overall structure.- Screen Definitions: Files within this directory (e.g.,
index.tsx,detail.tsx,filter-option.tsx) define the actual navigable screens of the gallery feature using Expo Router’s file-system-based routing. These screen files import and render the necessary UI components and invoke controllers from the core feature package. - Navigation Configuration: Leverages Expo Router to define navigation paths, screen presentation styles (e.g., modal, push), and parameter passing between screens.
- Platform-Specific Adaptations: While the core package aims for platform agnosticism, this layer may handle minor platform-specific UI tweaks or interactions if necessary, though this is generally minimized.
- Authentication & [Permissions](../Settings/Access control/AccessControlOverview.md): Integrates with the application’s overall authentication system (e.g.,
AuthGuard) to protect gallery screens and ensure that data is accessed only by authorized users according to their [permissions](../Settings/Access control/AccessControlOverview.md).
- Screen Definitions: Files within this directory (e.g.,
2.3. Interaction Flow and Design Rationale
The typical interaction flow underscores the separation of concerns:
- User Action: A user interacts with the Expo application, navigating to a gallery-related screen.
- Screen Rendering (App Layer): The corresponding screen component in
apps/expo/app/galleryis mounted by Expo Router. This screen component acts as a container. - Core Feature Invocation (Core Layer): The app-layer screen component imports and renders the main UI entry point from
packages/app/features/gallery(e.g.,GalleryScreen, which in turn rendersGallery). - UI & Logic Execution (Core Layer): The core gallery components handle all subsequent UI rendering, user interaction processing, data fetching initiation (via controllers and TanStack Query hooks), and state updates (via Jotai and component state).
- API Interaction: Controllers and query hooks, often using a repository pattern (
galleryRepository) and use cases (galleryUsecase), interact with backend API services to fetch or submit data. - Data Propagation: Fetched data or state changes flow reactively through the component tree, causing UI updates.
Design Rationale:
- Decoupling: This layered approach decouples the core gallery functionality from any specific application shell. This means the
packages/app/features/gallerycould theoretically be integrated into a web application or another mobile framework with a new, thin integration layer. - Focused Development: Developers can work on core gallery features (new components, data logic) without needing to deeply understand the intricacies of the entire Expo application, and vice-versa.
- Code Reusability: Common UI patterns (like filter selectors) and logic (data fetching, state management) are centralized in the core package, reducing duplication.
- Scalability of Teams: Different teams could potentially own different layers or aspects of the feature more easily.
2.5. Architecture Diagram
graph TD subgraph Expo Application Specifics B["apps/expo/app/gallery\nScreens & Navigation\n(Expo Router, AuthGuard)"] end subgraph Core Gallery Feature Package C["packages/app/features/gallery\nCore Logic & UI"] C --> D["UI Components (`./components/`)\ne.g., GalleryThumbnail, AttachmentViewer, Filters"] C --> E["Controllers & State (`./controller/`)\nJotai, TanStack Query, React Hook Form"] C --> G["Utility Functions (`./utils/`)\ne.g., Downloads, URL Gen, Formatters"] C --> H_Types["Type Definitions (`./type.ts`)"] end A[User Interacts with Expo App] --> B B --> D B --> E E --> F["API Services / Backend\ne.g., /v1.0/gallery/*, Thumbnail Worker, Download Worker"] D --> A E --> D G -.-> D G -.-> E style A fill:#lightgrey,stroke:#333,stroke-width:2px style B fill:#ccf,stroke:#333,stroke-width:2px style C fill:#f9f,stroke:#333,stroke-width:2px style F fill:#bbf,stroke:#333,stroke-width:2px style D fill:#cfc,stroke:#333,stroke-width:1px style E fill:#fec,stroke:#333,stroke-width:1px style G fill:#e6e6fa,stroke:#333,stroke-width:1px style H_Types fill:#fffacd,stroke:#333,stroke-width:1px classDef default stroke:#333,stroke-width:1px
This diagram provides a visual representation of the layered architecture, emphasizing the separation between the reusable core gallery package and its integration within the Expo application, as well as its interaction with backend services.
3. Core UI Components (packages/app/features/gallery/components/)
The ./components/ directory is rich with UI elements that construct the user experience of the gallery. These components are designed to be modular and reusable.
3.1. GalleryThumbnail.tsx
- Purpose: This is a fundamental component responsible for rendering individual thumbnail representations of media items within gallery grids or lists. It handles various display states, interactions, and visual cues.
- Key Props:
data: TData: Generic data object for the item, passed toonPress.source: ImageProps['source']: React Native Image source object (e.g.,{ uri: '...' }).width?: number,height?: number: Dimensions for the thumbnail container.selectMode?: boolean: If true, the component adapts its UI to indicate it’s part of a selection process (e.g., showing a checkbox area).selected?: boolean: If true (andselectModeis true), displays a visual cue (like a checkmark) indicating the item is selected.mediaType?: 'image' | 'video': Displays a small video icon overlay if the type is ‘video’.variant?: GalleryThumbnailVariant ('default' | 'stacked-rounded-bordered'): Modifies the thumbnail’s appearance. The ‘stacked’ variant provides a visual effect of multiple items grouped together.stackCount?: number: Ifvariantis ‘stacked’, this determines how many “stacked” elements are visually suggested.onPress?: (data: TData) => void: Callback function invoked when the thumbnail is pressed. Typically navigates to a detail view or toggles selection inselectMode.Overlay?: React.ReactNode: Allows rendering a custom React Node as an overlay on top of the thumbnail image (e.g., for additional icons or information).GalleryThumbnail.Overlayis a pre-defined overlay component.Footer?: React.ReactNode: Allows rendering a custom React Node below the thumbnail (e.g., for titles or dates).GalleryThumbnail.Detailsis a pre-defined footer.
- Internal Logic:
- Conditionally renders a selection overlay (
renderSelectOverlay) based onselectModeandselectedprops. - For
variant='stacked-rounded-bordered', it renders multiple underlying views (renderStackedVariant) to create a visual stacking effect, using transformations for perspective. - Manages different style sets (
containerVariantStylesMap,imageVariantStylesMap,shadowVariantStylesMap) based on thevariant.
- Conditionally renders a selection overlay (
- GitHub Link: packages/app/features/gallery/components/gallery-thumbnail.tsx
3.2. detail/AttachmentViewer.tsx
- Purpose: Displays a single, full-sized attachment, capable of rendering both images and videos. It’s the primary component for viewing media in detail.
- Key Props:
attachmentURL: string: The direct URL of the media file to be displayed.attachmentType: string ('photo' | 'video'): Determines whether to render an image viewer or a video player.
- Internal Logic:
- Uses a conditional rendering approach:
- If
attachmentTypeis ‘photo’, it renders a React NativeImagecomponent from@my/uistretched to fit its container. - If
attachmentTypeis ‘video’, it renders theVideocomponent fromexpo-av.useNativeControls={true}: Enables default video playback controls (play/pause, seek, volume).shouldPlay={true}: Attempts to auto-play the video when mounted.resizeMode={ResizeMode.CONTAIN}: Ensures the video fits within the component bounds while maintaining aspect ratio.- Includes an
onPlaybackStatusUpdatehandler to pause and reset video to the beginning when it finishes playing (if not looping).
- If
- Uses a conditional rendering approach:
- Styling: Typically styled to take up a significant portion of the screen, often with a dark background to emphasize the media content.
- GitHub Link: packages/app/features/gallery/components/detail/AttachmentViewer.tsx
3.3. gallery-menu.tsx
- Purpose: Provides the main interactive menu or toolbar for the gallery screen. This component typically includes search initiation, filter access, view options (like tile size), and actions like toggling selection mode.
- Key Features & Internal Logic:
- Search Bar: Integrates
@my/ui Searchcomponent. Tapping it navigates to a dedicated search screen (/home/gallery-searchor/gallery/search), passing current filter params. - Filter Navigation: A button (often an icon like
Settings2) navigates to the filter options screen (/gallery/filter-option), preserving existing filter parameters. - Tile Size Adjustment: If
libraryGroupByis ‘all’ or ‘days’, a button (e.g.,LayoutGridicon) opens aSheetcomponent. This sheet contains a@my/ui Sliderallowing the user to choose the number of columns for the thumbnail grid (e.g., 3 to 6). The selected size is stored inisTileSizeAtom(Jotai atom). - “More” Options & Select Mode: A “More” button (e.g.,
MoreVerticalicon) opens anotherSheet. This sheet contains an option to toggle “Select Mode.”- When “Select Mode” is activated (
setSelectMode(true)), the “More” button is replaced by a “Cancel” button. - The text for the “Select” option dynamically changes based on
libraryGroupBystate (e.g., “Select Attachments”, “Select Dates”).
- When “Select Mode” is activated (
- State Management: Uses
useAtom,useAtomValue,useSetAtomfrom Jotai to interact with global gallery state likeisTileSizeAtom,isTileSizeMenuOpenAtom,libraryGroupByAtom,librarySearchQueryAtom. - Routing: Uses
useRouter(from Solito) for navigation anduseGalleryParams(custom hook fromparams-controller.ts) to manage URL query parameters.
- Search Bar: Integrates
- GitHub Link: packages/app/features/gallery/components/gallery-menu.tsx
3.4. library/library-container.tsx
- Purpose: Acts as a dynamic container that renders different views of the gallery library based on the current grouping preference (e.g., all items, or items grouped by days, months, years, or sites).
- Key Features & Internal Logic:
- Dynamic View Rendering:
- It reads the
libraryGroupByAtom(Jotai state) which holds the current grouping mode (e.g., ‘all’, ‘days’, ‘months’, ‘years’, ‘sites’). - A
GroupByComponentobject maps these atom values to specific components:all: RendersGalleryLibraryAlldays: RendersGalleryLibraryDaysmonths: RendersGalleryLibraryMonthyears: RendersGalleryLibraryYear[sites](../Sites/SitesOverview.md): RendersGalleryAlbum(likely for site-based grouping)
- The selected component is then rendered, passing down necessary props like
selectModeandsetSelectMode.
- It reads the
- Floating Grouping Switcher:
- If
libraryGroupByis not ‘sites’ andselectModeis false, it displays aButtonFloatingGroupcomponent. - This floating group shows buttons for ‘All’, ‘Days’, ‘Months’, ‘Years’, allowing the user to switch the grouping.
- When a new grouping is selected, it updates
libraryGroupByAtom, invalidates relevant TanStack Query caches (queryClient.removeQueries({ queryKey: ['gallery'] })) to trigger fresh data loads for the new grouping, and disablesselectMode.
- If
- Dynamic View Rendering:
- Styling: The
FloatButtonContaineris a styledViewpositioned absolutely at the bottom-center of the container. - GitHub Link: packages/app/features/gallery/components/library/library-container.tsx
3.5. header.tsx (Standalone Gallery Header)
- Purpose: This is a general-purpose header component used to display a title, typically at the top of a gallery-related screen or section. It’s distinct from
GalleryMenuwhich is more interactive. - Key Props:
title?: string: The text to be displayed as the header’s title. If not provided, it defaults to a localized “Search Results” string (LL.gallery.searchResults()).
- Internal Logic:
- Uses
@my/ui TextandViewfor basic layout. - Accesses localized strings via
useLocalizationhook.
- Uses
- Common Usage: Displaying the title on the gallery search results screen.
- GitHub Link: packages/app/features/gallery/components/header.tsx
3.6. Filter Components (./filters/)
This sub-directory contains various components, each dedicated to a specific filter criterion. They are typically used within the GalleryFilterOptionScreen.
-
Example:
filters/attachment-type.tsx- Purpose: Allows users to filter gallery items by their media type (e.g., ‘image’, ‘video’).
- Key Features:
- Uses
react-hook-form’sControllercomponent to bind to the main filter form state managed byuseFilterFormContext. - Displays a
@my/ui ListItemwhich, when pressed, opens aMultiSelectorcomponent (presumably a custom or@my/uicomponent). - The
MultiSelectorpresents options like “Image” and “Video” (localized viaLL.gallery.filter.image()). - Selected values are propagated back to the form state via the
onChangecallback from theController. - Shows a count of selected types next to the list item.
- Uses
- GitHub Link: packages/app/features/gallery/components/filters/attachment-type.tsx
-
Example:
filters/date-filter.tsx- Purpose: Enables users to filter gallery items based on a selected date range (e.g., “Last 7 days”, “Custom Range”).
- Key Features:
- Also uses
react-hook-form’sControllerforstartDateandendDatefields. - Renders a
ListItem. Pressing it opens aFilterStatusSheet(a custom sheet component, likely fromapp/features/[report](../Reports/ReportsOverview.md)/filter-option/components/filter-by-date/sheet). - The
FilterStatusSheetprovides UI for selecting predefined date ranges or a custom start/end date. - Selected
startDateandendDateare updated in the form state. - A helper label (e.g., “This week”) is displayed on the
ListItemif the selected range matches a predefined option.
- Also uses
- GitHub Link: packages/app/features/gallery/components/filters/date-filter.tsx
Other filter components (e.g., for auditors, sites, departments, questionnaires) follow similar patterns, integrating with react-hook-form and using appropriate UI elements (often ListItem opening a selection sheet or modal) to capture user input.
4. Controllers and Data Management (packages/app/features/gallery/controller/)
This directory is pivotal for the gallery’s dynamic behavior, managing application state, data fetching logic, and complex interactions that go beyond simple UI rendering.
4.1. Global State Management with Jotai
The Gallery module leverages Jotai for managing global or widely shared state across its components and controllers. Jotai’s atomic approach allows for fine-grained state updates and optimized re-renders. Key atoms include:
libraryGroupByAtom: Stores the current grouping mode for the main gallery view (e.g., ‘all’, ‘days’, ‘months’, ‘years’, ‘sites’). Used byLibraryContainerto determine which view to render and by query hooks to pass the correctgroupByparameter to the API.isTileSizeAtom: Holds the user’s preferred number of columns for the thumbnail grid (e.g., 3, 4, 5, 6). Used byGalleryMenu’s tile size slider and consumed by grid rendering components to adjust layout.isTileSizeMenuOpenAtom: Boolean atom to control the visibility of the tile size selection sheet inGalleryMenu.librarySearchQueryAtom: Stores the current search string entered by the user. Consumed by query hooks to perform searches.galleryListingGroupByAtom: Potentially a separate atom for specifying grouping in search or listing contexts, distinct from the main library view’s grouping.galleryListingFromSearchAtom: Boolean, likely indicates if the current listing is a result of a search, which might alter query parameters or display logic.navBottomHeightAtom(fromapp/shared/state/home/home): While external to this feature’s direct state, it’s used byGalleryScreento adjust layout for bottom navigation, showing inter-feature state dependency.
These atoms are manipulated using Jotai hooks (useAtom, useSetAtom, useAtomValue) within various components and controllers, providing a reactive way to synchronize state across the module.
4.2. Parameter Management: params-controller.ts
- Purpose: This controller is essential for managing gallery route parameters, enabling filter criteria and other relevant state to be encoded within and read from the URL’s query string. This is crucial for features like shareable links that retain filter context and for persisting filter state across navigation sessions or page reloads (on web).
- Technology: Built using
solito’screateParam<GalleryRouteParams>()utility.GalleryRouteParamsis a TypeScript type defining the expected structure of gallery-related URL parameters (e.g.,startDate,endDate,type,siteId). - Key Exports:
useGalleryParam(paramName): Hook to access the value of a single, specific URL query parameter.useGalleryParams(): Hook to retrieve an object containing all current gallery-related URL query parameters. This is widely used by data fetching hooks to get filter criteria.useUpdateGalleryParams(): Hook that provides a function to update one or more URL query parameters, triggering a navigation event.
- Integration: Works closely with
filter-controller.tsto apply selected filters to the URL and with data fetching queries to consume these parameters. - GitHub Link: packages/app/features/gallery/controller/params-controller.ts
4.3. Filter Form Logic: filter-form-controller.ts
- Purpose: Centralizes the logic for the gallery’s filter form. It sets up and provides access to
react-hook-formfunctionalities. - Key Hook:
useFilterForm():- Likely initializes
react-hook-form’suseFormhook with default values for filter fields and potentially validation schemas (though not explicitly shown in provided code). - Returns the form methods (e.g.,
handleSubmit,control,reset,formState: { errors }) which are then used by individual filter components (AttachmentType,DateFilter, etc.) and thefilter-controller.
- Likely initializes
- Data Structure: The form manages an object whose fields correspond to the filterable attributes (e.g.,
type: string[],startDate: string,endDate: string,auditorId: string[]). - GitHub Link: packages/app/features/gallery/controller/filter-form-controller.ts
4.4. Filter Application Orchestration: filter-controller.ts
- Purpose: Acts as the bridge between the filter UI (where users make selections) and the application’s routing/data fetching mechanisms.
- Key Functions & Logic:
formMethods = useFilterForm(): Gets access to the filter form’s state and methods.navigateToFilteredScreen(params: GalleryRouteParams | {}): A core utility function that usesrouter.push(from Solito, likely viauseRouter) to change the current route. It constructs the target pathname (e.g.,/home/gallery) and appends the providedparamsobject as a query string. This is how selected filters are applied to the URL.resetFilter(): Clears all active filters by callingnavigateToFilteredScreenwith an empty params object.updateFilter(): This is the function called when the user submits the filter form. It’s typically assignedformMethods.handleSubmit((data) => navigateToFilteredScreen(data)).handleSubmitfromreact-hook-formensures any defined validation rules are run beforenavigateToFilteredScreenis called with the validated form data.
- Interaction: When
updateFilterorresetFiltercauses a URL change, TanStack Query hooks listening touseGalleryParams()will detect the change and trigger a data refetch. - GitHub Link: packages/app/features/gallery/controller/filter-controller.ts
4.5. Data Fetching with TanStack Query
The gallery module relies heavily on @tanstack/react-query for robust and efficient data fetching. This involves custom hooks that wrap useInfiniteQuery or useQuery.
-
all-gallery-query.ts:useAllGalleryQuery():- Purpose: Designed to fetch a comprehensive, paginated list of gallery items. It’s the primary data source for the main library views where items can be grouped by ‘days’, ‘months’, ‘years’, or ‘all’.
- Parameters & Logic:
- Retrieves filter parameters from
useGalleryParams(). - Reads the
libraryGroupByAtom(Jotai state) to determine the grouping. - Constructs a
queryParamsobject includinggroupBy,startDate(defaults to last week if not provided),endDate(defaults to today), and other filters fromparams. - Uses
useInfiniteQueryfor pagination.queryFn: Asynchronously callsgalleryUsecase.getFilteredGallery, passing a stringified version ofqueryParams(includingpageandlimit: 30).initialPageParam: 1: Starts fetching from page 1.getNextPageParam: Crucial for infinite scrolling. It inspects the last fetched page’s data (lastPage.data.data.totalPages) and the current page number (pageNofromlastPage.config.url) to determine if there’s a next page. It also includes a safeguard: if thegroupByparameter in the last request URL doesn’t match the currentlibraryGroupByAtomstate, it returnsundefinedto prevent incorrect pagination during state changes.queryKey:galleryQueryKey.GET_FILTERED_GALLERY(likely an array like['gallery', 'filtered']). This key is used by TanStack Query for caching and invalidation.
- Retrieves filter parameters from
- Return Value: Standard
useInfiniteQueryresult object containingdata(pages of results),fetchNextPage,hasNextPage,isLoading,isFetching, etc.
useAttachmentDetailQuery(attachmentId: string):- Purpose: Fetches detailed information for a single attachment, identified by
attachmentId. - Logic: Uses
useQuery(notuseInfiniteQueryas it’s a single item).queryFn: CallsgalleryUsecase.getAttachmentDetail({ attachmentId }).select: A selector function(data) => data?.data?.datato directly return the nested attachment data object from the API response.queryKey: An empty array[]is shown in the snippet, which is unusual. Typically, it would be something like[galleryQueryKey.GET_ATTACHMENT_DETAIL, attachmentId]. This might be a simplification or an issue in the provided code.
- Purpose: Fetches detailed information for a single attachment, identified by
- GitHub Link: packages/app/features/gallery/controller/all-gallery-query.ts
-
gallery-listing-query.ts(useGalleryListingQuery)- Purpose: Another hook for fetching paginated gallery items, but seems tailored for general listings and search results rather than the specific grouping views of
useAllGalleryQuery. - Parameters & Logic:
- Also uses
useGalleryParams()for base filter parameters. - Reads
galleryListingGroupByAtom,librarySearchQueryAtom, andgalleryListingFromSearchAtomfrom Jotai to adapt its behavior. queryParamsare constructed:groupBy: Is set togalleryListingGroupByifgalleryListingFromSearchis true, otherwise defaults to'all'. This allows search results to potentially have their own grouping independent of the main library view.search: Includes thesearchQueryif it’s present.
- Uses
useInfiniteQuerysimilarly touseAllGalleryQuery, callinggalleryUsecase.getFilteredGallerywith its constructedqueryParams. queryKey:galleryQueryKey.GET_GALLERY_LIST.
- Also uses
- GitHub Link: packages/app/features/gallery/controller/gallery-listing-query.ts
- Purpose: Another hook for fetching paginated gallery items, but seems tailored for general listings and search results rather than the specific grouping views of
-
Data Structures (Illustrative for
GalleryFilteredResponse):// Simplified - actual structure may vary based on 'GalleryFilteredResponse' type interface GalleryFilterResult { id: string; attachmentPath: string; thumbnailPath?: string; mediaType: 'image' | 'video'; fileName: string; fileSize: number; submittedDate: string; // ISO Date string siteID?: string; siteName?: string; // ... other metadata } interface GalleryFilteredResponseData { results: GalleryFilterResult[] | Record<string, GalleryFilterResult[]>; // Array or object if grouped by API totalPages: number; currentPage: number; totalItems: number; } interface GalleryFilteredApiResponse { data: GalleryFilteredResponseData; // ... other API response fields (status, message) }
4.6. Presentation Logic: gallery-listing-controller.tsx (GalleryListingController)
- Purpose: This component acts as a presentation controller or a custom hook. It consumes the raw data fetched by
useGalleryListingQueryand transforms it into various formats more suitable for direct rendering by different UI list components (e.g., flat lists, sectioned lists). - Key Logic & Transformations:
queryResult = useGalleryListingQuery(): Calls the data fetching hook.list: GalleryFilterResult[]: Memoized usinguseMemo. It flattens thedata.pagesarray from TanStack Query’s paginated result into a single, flat array of gallery items. This is suitable for simpleFlatListrenderings.sectionsObject: Record<string, GalleryFilterResult[]>: Memoized. This is more complex. It processesdata.pagesto group items into an object where keys are section titles (e.g., dates like “2023-10-26”, or site names if data is grouped by site at API level) and values are arrays of gallery items belonging to that section. This is useful for views that group items before passing to aSectionList.sections: Memoized. It further transformssectionsObjectinto an array format that is directly consumable by React Native’sSectionListcomponent (i.e.,[{ title: string, data: any[][] }]). Each item in thedatasub-array is also augmented with asectionId.
- Exposed Values: Returns an object containing the transformed
list,sections, andsectionsObject, along with the original query states fromuseGalleryListingQuery(e.g.,isFetching,hasNextPage,fetchNextPage,refetch,isLoading). UI components then destructure these values to render the gallery and handle interactions like pull-to-refresh or infinite scroll loading. - GitHub Link: packages/app/features/gallery/controller/gallery-listing-controller.tsx
4.7. Data Flow Summary (Reiteration with more detail)
- User Interaction/Initial Load: A gallery screen mounts or a user interacts (e.g., applies filter, scrolls, searches).
- Parameter/State Update:
- Filter changes:
filter-controllerupdates URL viaparams-controller. - Search: Search input updates
librarySearchQueryAtom. - Grouping change:
LibraryContainerupdateslibraryGroupByAtom.
- Filter changes:
- Query Trigger: TanStack Query hooks (
useAllGalleryQuery,useGalleryListingQuery) react to changes in their dependencies (URL params viauseGalleryParams, Jotai atoms). - API Call Construction: The active query hook constructs parameters (filters, page, limit, search terms, group criteria) and calls the appropriate method on
galleryUsecase. - Usecase & Repository:
galleryUsecasedelegates togalleryRepository, which makes the actual HTTP GET request usingapiClientto the defined endpoint (e.g.,/v1.0/gallery/gallery/filter). - API Response & Caching: The backend API responds with data. TanStack Query caches this data, manages pagination state, and provides status flags (loading, success, error).
- Data Transformation (Presentation Controller):
GalleryListingController(if used) takes the raw paginated data from the query hook and transforms it into flattened lists or sectioned data structures. - UI Rendering: React components (e.g.,
GalleryLibraryAll,GalleryLibraryDays, or custom lists usingGalleryThumbnail) receive the processed data and render the UI. Loading indicators, empty states, and error messages are also handled based on the query status. - Further Interactions:
fetchNextPageis called for infinite scrolling.refetchis called for pull-to-refresh.
This intricate system ensures that data fetching is efficient, state changes are managed reactively, and the UI remains responsive.
5. Utility Functions (packages/app/features/gallery/utils/)
The utils directory provides a collection of helper functions and custom hooks that encapsulate reusable logic, keeping other components and controllers cleaner and more focused on their primary responsibilities.
5.1. getGalleryThumbnailURL.ts
- Purpose: Centralizes the logic for constructing fully qualified URLs for accessing gallery media (thumbnails, full images, videos) via Nimbly’s thumbnail/media worker service.
- Key Functions:
getGalleryThumbnailUrl(item: GalleryFilterResult, size: number, quality: number = 60): string- Parameters:
item: AGalleryFilterResultobject containingattachmentPathandthumbnailPath.size: Target dimension (likely height or width) for the thumbnail.quality: JPEG quality percentage (default 60).
- Logic: Prefers
thumbnailPathif available, otherwise falls back toattachmentPath. Constructs the URL usingenvConfig.thumbnailWorkerUrl(the base URL of the media service) and appends the path along with query parameters for height (h=${size}%&${size}%- note: the double size param might be a specific requirement or a slight redundancy) and quality (q=${quality}). - Return: A string representing the full URL to fetch the thumbnail.
- Parameters:
getGalleryImagelUrl(item: GalleryFilterResult): string- Logic: Similar to
getGalleryThumbnailUrlbut omits sizing/quality parameters, intending to fetch the original or a default large version of an image. UsesattachmentPathorthumbnailPath. - Return: Full URL for the image.
- Logic: Similar to
getVideoUrl(item: GalleryFilterResult): string- Logic: Specifically uses
attachmentPathto construct the URL for a video file, also usingenvConfig.thumbnailWorkerUrlas the base. This implies the same worker service handles video delivery. - Return: Full URL for the video.
- Logic: Specifically uses
- Importance: By abstracting URL construction, this utility makes it easy to update media serving logic (e.g., CDN changes, parameter modifications) in one place.
- GitHub Link: packages/app/features/gallery/utils/getGalleryThumbnailURL.ts
5.2. downloadBulkAttachmentGallery.ts
- Purpose: Orchestrates the complex process of downloading multiple gallery attachments as a single ZIP archive. This utility handles API interaction, local file system operations, ZIP extraction, and platform-specific sharing/saving.
- Key Export:
downloadBulkAttachmentGallery(args: DownloadBulkAttachmentGalleryParams)DownloadBulkAttachmentGalleryParamsInterface:ids?: string[]: Array of specific attachment IDs to download.days?: Date[],months?: Date[],years?: Date[]: Arrays of Date objects to specify download by date criteria (formatted to ‘YYYY-MM-DD’).[sites](../Sites/SitesOverview.md)?: string[]: Array of site IDs.onProgress: RNFS.DownloadFileOptions['progress']: Callback for download progress updates.onBegin: RNFS.DownloadFileOptions['begin']: Callback when the download begins.
- Core Logic Steps:
- Parameter Processing: Filters out empty criteria and formats date parameters.
- API URL Construction: Builds the query string using
queryStringifyfrom the processed criteria and appends it toenvConfig.galleryDownloadWorkerUrl(base URL for the gallery download worker service). - File Paths: Defines temporary paths for the downloaded ZIP (
tempDownloadPath) and its extracted contents (tempExtractPath) usingexpo-file-systemorreact-native-fspaths, ensuring platform-compatibility (iOS vs. Android). It also creates necessary temporary “Nimbly” and “attachmentsTemp” directories if they don’t exist. - Cleanup: Calls
cleanUp()initially to remove any leftover temporary files from previous attempts. - Token Retrieval: Fetches JWT token using
getJwtToken(). - File Download (
downloadFileinternal function):- Uses
RNFS.downloadFileto download the ZIP from the constructed URL. Passes authorization token in headers. Thebeginandprogresscallbacks fromargsare wired here.
- Uses
- ZIP Extraction (
extractZipinternal function):- Reads the downloaded ZIP file as a Base64 string using
FileSystem.readAsStringAsync. - Uses
JSZipto load the Base64 data. - Iterates through each file in the unzipped archive:
- If it’s a directory, creates it using
FileSystem.makeDirectoryAsync. - If it’s a file, gets its data as
uint8array, converts to Base64 usinguint8ArrayToBase64(custom Base64 conversion), creates the target directory usingRNFS.mkdir, and writes the file usingRNFS.writeFile.
- If it’s a directory, creates it using
- Reads the downloaded ZIP file as a Base64 string using
- Platform-Specific Handling:
handleIOSDownload(tempExtractPath): Gets all file paths from the extracted folder. IfSharing.isAvailableAsync()is true, usesShare.open({ urls: files, saveToFiles: true })fromreact-native-shareto open the iOS share sheet, allowing user to save to Files, etc.handleAndroidDownload(tempExtractPath): Copies the entire extracted folder contents recursively to the publicRNFS.DownloadDirectoryPath + '/Nimbly'directory using a customcopyRecursivefunction (which usesRNFS.copyFileandRNFS.mkdir).
- Final Cleanup: Calls
cleanUp()again in afinallyblock to ensure temporary files are removed.
- Error Handling: Includes a
try...catch...finallyblock. Errors are logged to the console. - GitHub Link: packages/app/features/gallery/utils/downloadBulkAttachmentGallery.ts
5.3. getJwtToken.ts
- Purpose: Provides a straightforward way to access the current user’s JWT authentication token.
- Logic: Directly exports
authApiRepository.getJwtToken. - Deprecation Note: The file is marked with
@deprecated Use \authApiRepository.getJwtToken` instead.` This indicates a shift towards accessing the token directly from the authentication repository, promoting better encapsulation or a more centralized approach to auth concerns. - GitHub Link: packages/app/features/gallery/utils/getJwtToken.ts
5.4. getSectionLabel.ts
- Purpose: Generates user-friendly, localized date labels for section headers when gallery items are grouped by date (e.g., “Today”, “Yesterday”, “Monday”, “October 26”, “October 26, 2022”).
- Logic:
- Uses
moment.jsfor date parsing and manipulation. - Calculates the difference in days between the input
dateand the current date. - Returns “Today” if diff is 0, “Yesterday” if diff is 1.
- If diff is ⇐ 7 and the year is current, returns the day name (e.g., “Monday”).
- Otherwise, returns “Month Day” (e.g., “October 26”). If the year is different from current, appends ”, YYYY”.
- Uses
- Customization: Takes an optional
inputFormatfor the input date string. - GitHub Link: packages/app/features/gallery/utils/getSectionLabel.ts
5.5. useGalleryDownload.ts
- Purpose: A custom React hook that encapsulates the state and logic related to the user-facing gallery download feature. It simplifies the usage of
downloadBulkAttachmentGalleryin UI components by managing loading states, progress, and user feedback. - Parameters:
listData = []: Flat list of gallery items, used forcalculateItemsSize.sections = []: Sectioned list of gallery items, also forcalculateItemsSize.groupBy = '': Current grouping mode, used to determine how to pass item identifiers todownloadBulkAttachmentGallery.toastExternal = null: Allows providing an external toast controller, otherwise uses its ownuseToastController().
- Returned Values & State:
status: GalleryDownloadStatus ('zipping' | 'downloading' | 'idle' | 'error' | 'maximum'): Current status of the download operation.progress: number: Download progress percentage (0-100).errorMsg: string: Stores error messages if a download fails.handleDownload(ids: string[], fileSize: number): Function to initiate the download.calculateItemsSize(ids: string[]): Calculates total file size for selected items.
- Internal Logic of
handleDownload:- Resets
errorMsg. Validates ifidsare provided. - Determines
downloadItems(params fordownloadBulkAttachmentGallery) based ongroupBy:- If grouped by ‘months’, it calls
getDaysFromGroupByMonthsto get relevant day IDs. - If grouped by ‘sites’, it calls
getSiteIdsFromGroupBySiteto get site IDs. - Otherwise, it passes the raw
ids.
- If grouped by ‘months’, it calls
- Sets
statusto ‘zipping’. - Calls
downloadBulkAttachmentGallerywithdownloadItemsand wires uponBegin(setsstatusto ‘downloading’) andonProgress(updatesprogressstate based onbytesWrittenandfileSize). - On success, shows a toast message and resets
statusandprogress. - On error, sets
statusto ‘error’ and stores the error message. Handles a specific cancellation error code (ECANCELLED500).
- Resets
- Helper Functions:
getSiteIdsFromGroupBySiteandgetDaysFromGroupByMonthsextract relevant identifiers from thesectionsdata structure based on the selectedids. - GitHub Link: packages/app/features/gallery/utils/useGalleryDownload.ts
6. Screens and Navigation (apps/expo/app/gallery/)
The apps/expo/app/gallery/ directory serves as the integration point for the Gallery feature within the Nimbly Expo mobile application. It utilizes expo-router for its file-system-based routing mechanism, where each .tsx file typically corresponds to a specific route or screen.
A crucial aspect of this layer is the consistent use of AuthGuard (from app/shared/controller/[auth](../Authentication/AuthenticationOverview.md)/[auth](../Authentication/AuthenticationOverview.md)-guard), which wraps the screen components. This ensures that all gallery functionalities are accessible only to authenticated users, aligning with the application’s security model.
6.1. Main Gallery Screen (index.tsx)
- Purpose: This file defines the primary entry point or the “home screen” for the gallery feature. It’s what users typically see when they first navigate to the gallery section of the app. It’s responsible for displaying the main gallery interface where users can browse all their accessible media items, interact with filters, and initiate searches.
- Path: Conventionally,
index.tsxmaps to the base path of the directory, so/gallery. However, considering typical app structures, it might be nested under a tab navigator, making its effective path something like/home/gallery(as suggested by navigation paths inGalleryMenu.tsx). - Implementation Details:
- It renders the
<GalleryScreen />component, which is imported fromapp/features/gallery/screen(the core feature package). - The
<GalleryScreen />itself is a wrapper that primarily renders the main<Gallery />component (fromapp/features/gallery/gallery.tsx). This<Gallery />component is where theGalleryMenu,LibraryContainer,GalleryFooter, etc., are composed to form the complete gallery UI. - The entire content is wrapped within
<AuthGuard>...</AuthGuard>to ensure user authentication.
- It renders the
- Key User Interactions: Browsing media, opening the gallery menu for filtering, searching, changing view types.
- GitHub Link: apps/expo/app/gallery/index.tsx
6.2. Attachment Detail Screen (detail.tsx)
- Purpose: This screen is dedicated to displaying a single, specific gallery attachment in a focused, detailed view. This is typically navigated to when a user taps on a thumbnail in any of the gallery listing views.
- Path:
/gallery/detail. It’s expected that this route accepts parameters, most importantly an identifier for the attachment to be displayed (e.g.,/gallery/detail?attachmentId=123). These parameters are accessible viaexpo-router’s parameter hooks (e.g.,useLocalSearchParams). - Implementation Details:
- It renders the
<GalleryThumbnailDetails />component, imported fromapp/features/gallery/components/gallery-thumbnail-details. This core component is responsible for fetching the specific attachment’s data (if not already passed) and displaying it, likely usingAttachmentViewerinternally. - Includes
<Stack.Screen />fromexpo-router, which can be used to configure screen-specific options for the navigator stack (e.g., header title, presentation style), though no options are explicitly set in the snippet. - Also wrapped in
AuthGuard.
- It renders the
- Key User Interactions: Viewing the full-resolution media, potentially accessing metadata or actions related to that specific attachment (e.g., download, share - if implemented within
GalleryThumbnailDetails). - GitHub Link: apps/expo/app/gallery/detail.tsx
6.3. Filter Options Screen (filter-option.tsx)
- Purpose: This screen provides the user interface where various filter criteria can be selected and applied to the gallery listings. It aggregates different filter components (like date range, attachment type, site, etc.).
- Path:
/gallery/filter-option. This screen might also receive current filter parameters in its route params to pre-fill filter controls. - Implementation Details:
- It renders the
<GalleryFilterOptionScreen />component, imported fromapp/features/gallery/filter-option/index.tsx(within the core feature package). This component acts as a container for individual filter controls (e.g.,DateFilter,AttachmentTypeFilterfrompackages/app/features/gallery/components/filters/). - Uses
<Stack.Screen />for potential navigator configuration. - Wrapped in
AuthGuard.
- It renders the
- Key User Interactions: Selecting/deselecting filter values, applying filters (which typically navigates back to the main gallery screen with new filter params), clearing filters.
- GitHub Link: apps/expo/app/gallery/filter-option.tsx
6.4. Gallery Search Screen (search.tsx)
- Purpose: This screen is dedicated to the search functionality within the gallery. It typically includes a prominent search input field and an area to display the search results.
- Path:
/gallery/search. It might also be/home/gallery-searchif nested. Query parameters for existing filters might be passed to this screen to scope the search. - Implementation Details:
- It renders the
<GallerySearchScreen />component, imported fromapp/features/gallery/search/screen.tsx(core feature package). - This core screen component would contain the search input logic (likely updating
librarySearchQueryAtom) and the display of results, probably usingGalleryListingControlleror a similar mechanism configured for search. - Wrapped in
AuthGuard.
- It renders the
- Key User Interactions: Typing search queries, viewing search suggestions (if any), browsing search results, clearing search queries.
- GitHub Link: apps/expo/app/gallery/search.tsx
6.5. Navigation Flow Summary (Conceptual)
- Entry: User navigates to the Main Gallery Screen (
/galleryor/home/gallery). Data is loaded based on default or persisted filters. - Filtering:
- User taps filter icon in
GalleryMenu(on Main Gallery Screen). - Navigates to Filter Options Screen (
/gallery/filter-option), passing current filters. - User modifies filters and applies them.
- Navigates back to Main Gallery Screen, with new filter criteria appended as URL query parameters. The list refreshes.
- User taps filter icon in
- Searching:
- User taps search icon/bar in
GalleryMenu. - Navigates to Gallery Search Screen (
/gallery/search), possibly passing current filters. - User types query. Results update dynamically.
- User taps search icon/bar in
- Viewing Detail:
- From Main Gallery Screen or Gallery Search Screen, user taps a thumbnail.
- Navigates to Attachment Detail Screen (
/gallery/detail), passing the attachment’s ID.
- URL-Driven State: The use of URL parameters for filters (
params-controller.ts) is key, as it allows TanStack Query hooks to automatically refetch data when these parameters change, simplifying state synchronization for filtering.
This structure, leveraging expo-router and a clear separation between screen definitions (app layer) and feature implementation (core package), promotes organized navigation and maintainable code.
7. API Endpoints Used
The Gallery module interacts with several backend API endpoints to fetch data, retrieve media, and facilitate downloads. These interactions are generally abstracted through the galleryUsecase and galleryRepository layers. The base URL for these API endpoints is configured globally in the apiClient.
| Endpoint Path | HTTP Method | Purpose | Request Params (Typical Structure) | Response (Brief Structure - Key Fields) | Consuming File(s) in Gallery Feature |
|---|---|---|---|---|---|
/v1.0/gallery/gallery/filter | GET | Fetches a paginated and filtered list of gallery items. | Query string. E.g., groupBy=days&startDate=2023-01-01&endDate=2023-01-31&page=1&limit=30&type=image&siteId=some-[site](../Sites/SitesOverview.md)-id | data: { results: GalleryFilterResult[] or Record<string, GalleryFilterResult[]>, totalPages: number, currentPage: number } | all-gallery-query.ts, gallery-listing-query.ts (via usecase & repo) |
/v1.0/gallery/gallery/{attachmentId} | GET | Fetches detailed information for a specific attachment. | attachmentId as part of the URL path. | data: GalleryDetailsResponse (contains full metadata for the attachment) | all-gallery-query.ts (for useAttachmentDetailQuery via usecase & repo) |
/v1.0/gallery/gallery/search | GET | Fetches gallery items based on a search query and other active filters. | Query string. E.g., search=keyword&type=video&page=1&limit=30 | data: GallerySearchStore (similar to GalleryFilteredResponse, tailored for search) | gallery-listing-query.ts (when searchQuery is present, via usecase & repo) |
{envConfig.thumbnailWorkerUrl}/{mediaPath} | GET | Retrieves thumbnail or full-size image/video content. | mediaPath from item data. Query params like h=100&w=100&q=60 for thumbnails. No specific params for full size in some cases. | Binary data (image/video stream). | getGalleryThumbnailURL.ts, AttachmentViewer.tsx (indirectly) |
{envConfig.galleryDownloadWorkerUrl} | GET | Downloads a ZIP file containing multiple attachments based on criteria. | Query string with criteria. E.g., ids=id1,id2 or days=2023-10-10&[sites](../Sites/SitesOverview.md)=siteA | Binary data (ZIP file stream). | downloadBulkAttachmentGallery.ts |
Notes on API Interactions:
apiClient: All calls to the/v1.0/gallery/...endpoints are made through a centralizedapiClientwhich handles base URL configuration, authentication headers (JWT token), and potentially common error handling or interceptors.envConfigURLs:envConfig.thumbnailWorkerUrl: Points to a specialized microservice or CDN responsible for on-the-fly thumbnail generation, image resizing, and serving media content. This allows the main backend to offload these potentially intensive tasks.envConfig.galleryDownloadWorkerUrl: Points to a service that can take multiple attachment identifiers or criteria, gather the corresponding files, zip them, and stream the archive back. This is more efficient than client-side zipping for large numbers of files.
- Data Structures:
GalleryFilterResult: Represents a single item in a gallery listing. Typically includesid,attachmentPath,thumbnailPath,mediaType,fileName,fileSize,submittedDate, and related entity IDs/names (site, auditor, etc.).GalleryDetailsResponse: Contains more exhaustive metadata for a single attachment when viewed in detail.- The structure of
resultsinGalleryFilteredResponsecan vary: an array if not grouped by the API, or an object where keys are group names (e.g., dates) and values are arrays ofGalleryFilterResultif the API performs the grouping.
- Error Handling: While not explicitly detailed in the endpoint list, error handling for API requests is managed by TanStack Query (which provides error states) and potentially global error interceptors in
apiClient. Specific UI feedback for errors is handled in components consuming the query hooks or utility functions.
8. Key Package Dependencies
The Gallery module leverages a combination of internal Nimbly libraries and reputable third-party packages to deliver its rich feature set. Understanding these dependencies is crucial for developers working on this module.
| Package Name | Purpose in Gallery Module & Significance | Key Areas of Usage (Examples) |
|---|---|---|
@my/ui | Nimbly’s internal UI component library. Provides a consistent set of foundational UI elements (Stack, Text, Button, Sheet, Modal, ListItem, Search, Image, Slider etc.), ensuring visual and interaction consistency with the rest of the application. Essential for building the entire gallery interface. | Used extensively in nearly all files within packages/app/features/gallery/components/ and screen definitions. |
@tamagui/lucide-icons | Icon Library. Provides a comprehensive set of high-quality, configurable SVG icons (like Grid2x2, Settings2, ChevronRight) used for buttons, indicators, and visual cues throughout the gallery interface, enhancing usability and visual appeal. | GalleryMenu.tsx, AttachmentType.tsx, various filter components, list items. |
expo-av | Expo Audio/Video Library. Enables video playback functionality directly within the application. Specifically used for the Video component to render and control video attachments. | AttachmentViewer.tsx for displaying video files. |
expo-file-system | Expo File System API. Provides access to the local file system on the device. Used for managing temporary files during bulk downloads, reading downloaded ZIPs for extraction, and creating directories. | downloadBulkAttachmentGallery.ts for reading/writing temporary files and creating directories. |
expo-sharing | Expo Sharing API. Allows sharing of files with other apps on the device. Primarily used on iOS within the bulk download feature to present the native share sheet for saved/extracted attachments. | downloadBulkAttachmentGallery.ts for the iOS sharing mechanism. |
jotai | Atomic State Management Library. Used for managing global and shared state within the gallery (e.g., current tile size, search query, grouping preferences). Its atomic nature helps in optimizing re-renders and managing state in a predictable way. | GalleryMenu.tsx, LibraryContainer.tsx, various query files (all-gallery-query.ts) that depend on global gallery settings. |
@tanstack/react-query | Data Fetching and Caching Library. Powers all interactions with backend APIs for fetching gallery lists, details, and search results. Manages caching, background updates, pagination, infinite scrolling, and request statuses (loading, error, success). | all-gallery-query.ts, gallery-listing-query.ts, GalleryListingController.tsx. Crucial for data layer. |
react-hook-form | Form State Management Library. Manages the state, validation, and submission of filter forms within the gallery, simplifying complex form logic and improving user experience with clear validation feedback. | AttachmentType.tsx, DateFilter.tsx, and other filter components; centralized via filter-form-controller.ts. |
solito | Cross-Platform Navigation Library (React Native + Next.js). Facilitates navigation and parameter management in a way that’s compatible with both native and web platforms. Used for routing between gallery screens and for encoding/decoding filter state in URL parameters. | params-controller.ts for URL parameter management, and implicitly by expo-router for screen navigation. |
jszip | ZIP File Manipulation Library. Used for client-side extraction of ZIP files received from the bulk download endpoint. It can read and decompress ZIP archives in JavaScript. | downloadBulkAttachmentGallery.ts for unzipping the downloaded attachment archive. |
react-native-fs | File System Access for React Native. Provides more extensive file system APIs for native platforms (iOS/Android) compared to expo-file-system in some cases. Used for downloading files (via downloadFile) and managing directories/files in the bulk download process, especially on Android. | downloadBulkAttachmentGallery.ts for downloading files and managing file system operations on native. |
react-native-share | Cross-Platform Sharing Library. Provides a unified API to trigger native sharing dialogs on iOS and Android. Used in the bulk download feature to share multiple files. | downloadBulkAttachmentGallery.ts as part of the file sharing mechanism after download and extraction. |
dayjs / moment | Date/Time Manipulation Libraries. Used for formatting, parsing, and manipulating dates. dayjs is noted in newer query files for parameter formatting (lightweight). moment is seen in older utility functions like downloadBulkAttachmentGallery (timestamping) and getSectionLabel (more complex date formatting). The presence of both suggests a gradual migration or specific feature needs. | all-gallery-query.ts (dayjs), downloadBulkAttachmentGallery.ts (moment), getSectionLabel.ts (moment). |
query-string | URL Query String Manipulation Utility. Used for parsing query strings from URLs (less common in this module) and for robustly stringifying JavaScript objects into URL query parameters for API requests. | all-gallery-query.ts, gallery-listing-query.ts for creating API request URLs; downloadBulkAttachmentGallery.ts. |
This list highlights the critical tools and libraries that underpin the gallery’s functionality. A working knowledge of these is beneficial for any developer contributing to this module. Versioning is managed project-wide in package.json and lock files.
9. Key Logics and Flows
This section details some of the most important logical flows within the Gallery module, illustrating how different components, controllers, and utilities interact to achieve key functionalities. Understanding these flows is crucial for debugging and extending the module.
9.1. Image/Video Loading and Display Flow
This flow describes the journey of a media item from its raw data form to being displayed as a thumbnail and eventually as a full-view interactive element.
-
Thumbnail Display:
- Data Acquisition: Data fetching hooks like
useAllGalleryQueryoruseGalleryListingQueryretrieve a list ofGalleryFilterResultobjects from the backend. Each object contains metadata likeattachmentPathandthumbnailPath. - Component Rendering: A list component (e.g., inside
GalleryLibraryAllor search results) iterates over this data, rendering aGalleryThumbnailcomponent for each item. - URL Construction: The
GalleryThumbnailcomponent, upon receiving item data, invokesgetGalleryThumbnailUrl(item, desiredSize, quality)frompackages/app/features/gallery/utils/getGalleryThumbnailURL.ts. This utility constructs a full URL pointing toenvConfig.thumbnailWorkerUrl, appending the item’s media path and query parameters for specific dimensions (e.g.,h=200&w=200) and compression quality. - Image Rendering: The React Native
Imagecomponent (or a custom wrapper like@my/ui Image) withinGalleryThumbnailuses the generated URL in itssourceprop. The mobile OS then handles the asynchronous fetching and decoding of the image from this URL. - Video Icon: If
item.mediaTypeis ‘video’,GalleryThumbnailoverlays a video icon, indicating the media type without loading the video content itself for the thumbnail.
- Data Acquisition: Data fetching hooks like
-
Full View Display (via
AttachmentViewer):- User Interaction: The user taps on a
GalleryThumbnail. TheonPresshandler of the thumbnail is triggered. - Navigation: Typically, the
onPresshandler initiates navigation to the attachment detail screen (e.g.,/gallery/detail), passing the unique identifier of the tapped attachment (e.g.,attachmentId) as a route parameter. - Detail Screen Mounting: The
detail.tsxscreen component mounts. It retrieves theattachmentIdfrom route parameters. - Data Fetching (if needed): The
GalleryThumbnailDetailscomponent (rendered bydetail.tsx) might useuseAttachmentDetailQuery(attachmentId)to fetch comprehensive metadata for this specific attachment if not all data was available from the list item. - Viewer Rendering:
GalleryThumbnailDetailsthen renders theAttachmentViewercomponent, passing theattachmentURL(either from the fetched detail or constructed usinggetGalleryImagelUrlorgetVideoUrlfor consistency) andattachmentType. - Media Display:
- Image: If
attachmentTypeis ‘photo’,AttachmentVieweruses a React NativeImagecomponent to display the full-resolution image fromattachmentURL. - Video: If
attachmentTypeis ‘video’,AttachmentVieweruses theVideocomponent fromexpo-av. It setssource={{ uri: attachmentURL }}, enablesuseNativeControls, and oftenshouldPlay={true}to begin playback.ResizeMode.CONTAINensures the video fits the allocated space while maintaining aspect ratio. Playback status updates (like end of video) are handled to provide a polished experience (e.g., resetting video to start).
- Image: If
- User Interaction: The user taps on a
sequenceDiagram participant User participant ListingScreen as "Gallery List/Search Screen" participant GalleryThumbnail participant GetURLUtil as "getGalleryThumbnailUrl()" participant RNImageLib as "React Native Image Lib" participant DetailScreen as "Attachment Detail Screen" participant AttachmentViewer participant ExpoAVVideoLib as "expo-av Video Lib" participant DataQueryHooks as "TanStack Query Hooks" participant API User->>ListingScreen: Views gallery ListingScreen->>DataQueryHooks: Request media list DataQueryHooks->>API: Fetch paginated data API-->>DataQueryHooks: Return GalleryFilterResult[] DataQueryHooks-->>ListingScreen: Provide data ListingScreen->>GalleryThumbnail: Render item (props: itemData) GalleryThumbnail->>GetURLUtil: Get URL for itemData GetURLUtil-->>GalleryThumbnail: Return formatted thumbnail URL GalleryThumbnail->>RNImageLib: Display image from URL User->>GalleryThumbnail: Taps thumbnail GalleryThumbnail-->>ListingScreen: Notify navigation (pass item.id) ListingScreen-->>DetailScreen: Navigate with attachmentId DetailScreen->>DataQueryHooks: (Optional) Request full attachment details for attachmentId DataQueryHooks->>API: (Optional) Fetch full details API-->>DataQueryHooks: (Optional) Return GalleryDetailsResponse DataQueryHooks-->>DetailScreen: Provide full item data DetailScreen->>AttachmentViewer: Render with itemData (URL, type) alt Item is Image AttachmentViewer->>RNImageLib: Displays full image from itemData.attachmentURL else Item is Video AttachmentViewer->>ExpoAVVideoLib: Play video from itemData.attachmentURL end
9.2. Filter Application Flow
This flow details how users apply filters and how the gallery display updates accordingly, emphasizing the URL-driven state.
- Filter Initiation: The user is on a gallery listing screen (e.g., Main Gallery). They tap the “Filter” icon/button within the
GalleryMenucomponent. - Navigation to Filter UI: The
GalleryMenu’s action handler triggers a navigation event (usingrouter.pushfrom Solito) to the dedicated filter options screen (/gallery/filter-option). Current filter parameters (obtained viauseGalleryParams) are often passed as route parameters to this screen to pre-fill existing filter selections. - User Filter Selection: On the
Filter Options Screen(which rendersGalleryFilterOptionScreen), the user interacts with various filter components (e.g.,DateFilter,AttachmentTypeFilter,SiteFilter). Each of these components is typically a controlled input, managed byreact-hook-formunder theFilterFormContextprovided byuseFilterFormfromfilter-form-controller.ts. As the user makes selections, the local form state is updated. - Filter Submission: The user confirms their filter choices, usually by tapping an “Apply” or “Done” button on the
Filter Options Screen. This action calls theupdateFilterfunction obtained fromuseFilterController(). - URL Parameter Update:
- The
updateFilterfunction (which isformMethods.handleSubmit(navigateToFilteredScreen)) first triggersreact-hook-form’s validation. - If validation passes,
navigateToFilteredScreen(validatedFilterData)is called. navigateToFilteredScreen(withinfilter-controller.ts) usesrouter.pushto navigate back to the main gallery listing screen (e.g.,/gallery). Crucially, it appends thevalidatedFilterDataobject to the URL as query parameters (e.g.,/gallery?type=image&startDate=...). This update is facilitated byuseUpdateGalleryParams.
- The
- Reactive Data Refetch:
- The main gallery listing screen’s data fetching hook (e.g.,
useAllGalleryQueryoruseGalleryListingQuery) hasuseGalleryParams()as a dependency. - When the URL query parameters change due to the navigation in step 5,
useGalleryParams()provides new parameter values. - This change in dependency automatically triggers TanStack Query to refetch the data. The query hook constructs a new API request to
/v1.0/gallery/gallery/filter, this time including the newly selected filter criteria from the URL.
- The main gallery listing screen’s data fetching hook (e.g.,
- UI Update with Filtered Data:
- The API returns the filtered dataset.
- TanStack Query updates its cache and provides the new data to the hook’s consumers.
- Components like
GalleryListingControllerprocess this new data, and the UI re-renders to display only the gallery items that match the applied filters. - If no items match, an appropriate empty state message is typically shown.
- Error Handling: Validation errors during filter submission are handled by
react-hook-formand displayed within theFilter Options Screen. API errors during data refetch are caught by TanStack Query and can be used to show error messages on the gallery listing screen.
graph LR subgraph MainGalleryScreen["Main Gallery Screen"] direction LR MG_UI["UI (Gallery Items)"] MG_Menu["GalleryMenu Component"] MG_QueryHook["Data Query Hook (e.g., useAllGalleryQuery)"] MG_Params["useGalleryParams()"] end subgraph FilterOptionScreen["Filter Options Screen"] direction LR FO_UI["Filter UI (Date, Type etc.)"] FO_Form["react-hook-form (useFilterForm)"] FO_Controller["filter-controller (updateFilter)"] end User -- 1. Taps Filter Icon --> MG_Menu; MG_Menu -- 2. Navigates with current params --> FO_UI; User -- 3. Interacts --> FO_UI; FO_UI -- Updates --> FO_Form; User -- 4. Applies Filters --> FO_Controller; FO_Controller -- 5. Navigates to MainGalleryScreen with new URL Query Params --> MG_Params; MG_Params -- 6. Detects URL Change --> MG_QueryHook; MG_QueryHook -- 6a. Refetches from API with new filters --> API["API (/v1.0/gallery/gallery/filter)"]; API -- 6b. Returns Filtered Data --> MG_QueryHook; MG_QueryHook -- 7. Provides new data --> MG_UI; MG_UI -- 7a. Re-renders with filtered items --> User; style MG_Params fill:#eee,stroke:#333 style FO_Form fill:#eee,stroke:#333
9.3. Search Functionality Flow
This flow outlines how users search for specific media items.
- Search Initiation: The user taps the search bar/icon within the
GalleryMenuon a gallery listing screen or directly starts typing if the search bar is always active. - Navigation to Search Screen (if applicable): Often, activating search navigates the user to a dedicated search screen (
/gallery/searchor/home/gallery-search). This screen is specifically designed for search interaction and results display. The current filter parameters might be carried over to this search screen to scope the search. - Query Input: The user types their search terms into the search input field on the search screen.
- State Update: The text from the search input field is used to update a global Jotai state atom, typically
librarySearchQueryAtom. This update is usually debounced to avoid excessive API calls while the user is typing. - Reactive Data Fetching for Search:
- The
useGalleryListingQuery(or a similar hook dedicated to search, likeuseGallerySearchQueryifgalleryUsecase.getGallerySearchis used) on the search screen subscribes tolibrarySearchQueryAtom. - When
librarySearchQueryAtomchanges (after debouncing), the query hook includes the new search term as asearchparameter (and any active filters) in its API request to/v1.0/gallery/gallery/filteror/v1.0/gallery/gallery/search.
- The
- API Returns Search Results: The backend processes the search query against relevant fields (filenames, metadata, tags etc.) and returns a list of matching gallery items.
- Displaying Results: The search screen receives the new data from the query hook (potentially processed by
GalleryListingController). It then updates the UI to display the search results, often in a list format usingGalleryThumbnailcomponents. If no results are found, an appropriate “no results” message is shown. - Clearing Search: The user can clear the search query, which would typically reset
librarySearchQueryAtom, causing the list to either show all items again (respecting filters) or a default state for the search screen.
sequenceDiagram participant User participant GalleryScreen participant GalleryMenu participant SearchScreen participant SearchInput participant JotaiState as "librarySearchQueryAtom" participant SearchQueryHook as "useGalleryListingQuery (with search logic)" participant API User->>GalleryMenu: Taps search icon or types GalleryMenu->>SearchScreen: Navigates (optional, or search UI is part of GalleryScreen) SearchScreen->>SearchInput: User types search query "XYZ" SearchInput-->>JotaiState: Updates atom with "XYZ" (debounced) JotaiState-->>SearchQueryHook: Notifies of new search query SearchQueryHook->>API: Fetches data (params: {search: "XYZ", ...filters}) API-->>SearchQueryHook: Returns matching results SearchQueryHook-->>SearchScreen: Provides search results SearchScreen-->>User: Displays results / No results message
9.4. Bulk Download Flow
This flow describes the process when a user selects multiple items or a logical group (like a whole day’s or a site’s attachments) for download.
- Item Selection: The user enters “Select Mode” (usually via
GalleryMenu). They then tap on multipleGalleryThumbnailcomponents or select entire groups (if the UI supports group selection, e.g., selecting a whole day inLibraryDaysview). The IDs of these selected items/groups are collected. - Download Initiation: User taps a “Download” button, which becomes active during select mode or is available for a group. This action calls the
handleDownload(selectedIds, totalSizeEstimate)function provided by theuseGalleryDownloadhook. useGalleryDownloadHook Activation:- The hook sets its internal
statusto ‘zipping’ (or similar initial state) andprogressto 0. - It determines the exact parameters for
downloadBulkAttachmentGallerybased on the currentgroupBystate and the nature ofselectedIds(are they individual file IDs, or representations of days/months/sites?). This might involve transforming group selections into concrete file identifiers or specific date/site parameters for the API.
- The hook sets its internal
- Calling
downloadBulkAttachmentGalleryUtility: The hook invokesdownloadBulkAttachmentGallerywith the processed parameters and callbacks foronBeginandonProgress. - API Call for ZIP:
downloadBulkAttachmentGalleryconstructs a URL forenvConfig.galleryDownloadWorkerUrlwith query parameters specifying the attachments to be included in the ZIP. It makes an authorized GET request. - Streaming Download & Progress:
- The
RNFS.downloadFilefunction handles the download of the ZIP stream from the API. - The
onBegincallback (fromuseGalleryDownload) is triggered, settingstatusto ‘downloading’. - The
onProgresscallback continuously updates theprogressstate inuseGalleryDownload, which can be used to render a progress bar in the UI.
- The
- ZIP Extraction: Once the ZIP file is fully downloaded to a temporary local path,
downloadBulkAttachmentGalleryusesJSZipto read and extract its contents into another temporary local directory. Each file within the archive is written out. - File System Access & Sharing:
- Android: The extracted files are recursively copied from the temporary extraction directory to a publicly accessible “Nimbly” folder within the device’s standard “Download” directory using
RNFS. The user is typically notified via a toast message. - iOS: The
Share.open({ urls: arrayOfLocalFilePaths, saveToFiles: true })function fromreact-native-share(orexpo-sharing) is used. This presents the native iOS Share Sheet, from which the user can choose to save the files to their Files app, AirDrop them, or send them to other compatible apps.
- Android: The extracted files are recursively copied from the temporary extraction directory to a publicly accessible “Nimbly” folder within the device’s standard “Download” directory using
- Feedback and Cleanup:
- Upon successful completion of sharing/saving,
useGalleryDownloadupdates itsstatusto ‘idle’ or ‘success’ and displays a success toast message. - If any step fails (network error, file system error, extraction error),
statusis set to ‘error’,errorMsgis populated, and an error toast is shown. - The
finallyblock indownloadBulkAttachmentGalleryensures that all temporary ZIP files and extracted content directories are deleted from the local file system, regardless of success or failure.
- Upon successful completion of sharing/saving,
graph TD A[User Selects Items/Groups in Select Mode] --> B{Triggers Download Action} B --> C[Download Handler: useGalleryDownload.handleDownload] C --> UI1[UI: Shows Zipping Indicator] C --> D[downloadBulkAttachmentGallery util] D --> E[API: galleryDownloadWorkerUrl] E --> F[RNFS.downloadFile] F -->|onBegin: status='downloading'| C F -->|onProgress: updates progress| C C --> UI1 F -->|.zip fully downloaded| D D --> G[Extract .zip using JSZip] G --> H{Platform-Specific Handling} H --> I[Android: RNFS - Copy to /Download/Nimbly] H --> J[iOS: react-native-share - Open Share Sheet] I --> K[Success/Error Feedback] J --> K K --> C C --> UI2[UI: Toast / Reset State] D --> L[Clean up temp files] style UI1 fill:#lightblue style UI2 fill:#lightblue
These detailed flows illustrate the intricate coordination between UI components, state management, controllers, utilities, and backend services that together constitute the Gallery module’s functionality.
10. Conclusion
The Gallery module, as detailed in this document, represents a significant and complex feature within the Nimbly application. Its architecture emphasizes modularity and reusability, with a clear separation between the core feature logic/UI and its integration into the Expo application. Key strengths include its robust data fetching and caching powered by TanStack Query, flexible state management via Jotai, and a comprehensive set of UI components and utility functions that cater to a wide range of media interaction needs.
Current State Summary:
- The module successfully provides functionalities for browsing, filtering, searching, viewing, and bulk downloading various media attachments.
- It employs a layered architecture making it maintainable and scalable.
- Key technologies like TanStack Query, Jotai, Expo AV, and Expo FileSystem are well-integrated.
- Detailed flows for image loading, filtering, search, and bulk download have been established.
This technical documentation serves as a living document. As the Gallery module evolves, this documentation should be updated to reflect new features, architectural changes, and refined logic, ensuring it remains a valuable resource for the development team.
This concludes the technical documentation for the Gallery module.