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:

  1. 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.
  2. 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).

2.3. Interaction Flow and Design Rationale

The typical interaction flow underscores the separation of concerns:

  1. User Action: A user interacts with the Expo application, navigating to a gallery-related screen.
  2. Screen Rendering (App Layer): The corresponding screen component in apps/expo/app/gallery is mounted by Expo Router. This screen component acts as a container.
  3. 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 renders Gallery).
  4. 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).
  5. 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.
  6. 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/gallery could 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 to onPress.
    • 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 (and selectMode is 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: If variant is ‘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 in selectMode.
    • 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.Overlay is a pre-defined overlay component.
    • Footer?: React.ReactNode: Allows rendering a custom React Node below the thumbnail (e.g., for titles or dates). GalleryThumbnail.Details is a pre-defined footer.
  • Internal Logic:
    • Conditionally renders a selection overlay (renderSelectOverlay) based on selectMode and selected props.
    • 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 the variant.
  • 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 attachmentType is ‘photo’, it renders a React Native Image component from @my/ui stretched to fit its container.
      • If attachmentType is ‘video’, it renders the Video component from expo-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 onPlaybackStatusUpdate handler to pause and reset video to the beginning when it finishes playing (if not looping).
  • 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
  • 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 Search component. Tapping it navigates to a dedicated search screen (/home/gallery-search or /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 libraryGroupBy is ‘all’ or ‘days’, a button (e.g., LayoutGrid icon) opens a Sheet component. This sheet contains a @my/ui Slider allowing the user to choose the number of columns for the thumbnail grid (e.g., 3 to 6). The selected size is stored in isTileSizeAtom (Jotai atom).
    • “More” Options & Select Mode: A “More” button (e.g., MoreVertical icon) opens another Sheet. 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 libraryGroupBy state (e.g., “Select Attachments”, “Select Dates”).
    • State Management: Uses useAtom, useAtomValue, useSetAtom from Jotai to interact with global gallery state like isTileSizeAtom, isTileSizeMenuOpenAtom, libraryGroupByAtom, librarySearchQueryAtom.
    • Routing: Uses useRouter (from Solito) for navigation and useGalleryParams (custom hook from params-controller.ts) to manage URL query parameters.
  • 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 GroupByComponent object maps these atom values to specific components:
        • all: Renders GalleryLibraryAll
        • days: Renders GalleryLibraryDays
        • months: Renders GalleryLibraryMonth
        • years: Renders GalleryLibraryYear
        • [sites](../Sites/SitesOverview.md): Renders GalleryAlbum (likely for site-based grouping)
      • The selected component is then rendered, passing down necessary props like selectMode and setSelectMode.
    • Floating Grouping Switcher:
      • If libraryGroupBy is not ‘sites’ and selectMode is false, it displays a ButtonFloatingGroup component.
      • 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 disables selectMode.
  • Styling: The FloatButtonContainer is a styled View positioned absolutely at the bottom-center of the container.
  • GitHub Link: packages/app/features/gallery/components/library/library-container.tsx
  • 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 GalleryMenu which 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 Text and View for basic layout.
    • Accesses localized strings via useLocalization hook.
  • 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’s Controller component to bind to the main filter form state managed by useFilterFormContext.
      • Displays a @my/ui ListItem which, when pressed, opens a MultiSelector component (presumably a custom or @my/ui component).
      • The MultiSelector presents options like “Image” and “Video” (localized via LL.gallery.filter.image()).
      • Selected values are propagated back to the form state via the onChange callback from the Controller.
      • Shows a count of selected types next to the list item.
    • 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’s Controller for startDate and endDate fields.
      • Renders a ListItem. Pressing it opens a FilterStatusSheet (a custom sheet component, likely from app/features/[report](../Reports/ReportsOverview.md)/filter-option/components/filter-by-date/sheet).
      • The FilterStatusSheet provides UI for selecting predefined date ranges or a custom start/end date.
      • Selected startDate and endDate are updated in the form state.
      • A helper label (e.g., “This week”) is displayed on the ListItem if the selected range matches a predefined option.
    • 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 by LibraryContainer to determine which view to render and by query hooks to pass the correct groupBy parameter to the API.
  • isTileSizeAtom: Holds the user’s preferred number of columns for the thumbnail grid (e.g., 3, 4, 5, 6). Used by GalleryMenu’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 in GalleryMenu.
  • 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 (from app/shared/state/home/home): While external to this feature’s direct state, it’s used by GalleryScreen to 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’s createParam<GalleryRouteParams>() utility. GalleryRouteParams is 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.ts to 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-form functionalities.
  • Key Hook: useFilterForm():
    • Likely initializes react-hook-form’s useForm hook 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 the filter-controller.
  • 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 uses router.push (from Solito, likely via useRouter) to change the current route. It constructs the target pathname (e.g., /home/gallery) and appends the provided params object as a query string. This is how selected filters are applied to the URL.
    • resetFilter(): Clears all active filters by calling navigateToFilteredScreen with an empty params object.
    • updateFilter(): This is the function called when the user submits the filter form. It’s typically assigned formMethods.handleSubmit((data) => navigateToFilteredScreen(data)). handleSubmit from react-hook-form ensures any defined validation rules are run before navigateToFilteredScreen is called with the validated form data.
  • Interaction: When updateFilter or resetFilter causes a URL change, TanStack Query hooks listening to useGalleryParams() 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 queryParams object including groupBy, startDate (defaults to last week if not provided), endDate (defaults to today), and other filters from params.
        • Uses useInfiniteQuery for pagination.
          • queryFn: Asynchronously calls galleryUsecase.getFilteredGallery, passing a stringified version of queryParams (including page and limit: 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 (pageNo from lastPage.config.url) to determine if there’s a next page. It also includes a safeguard: if the groupBy parameter in the last request URL doesn’t match the current libraryGroupByAtom state, it returns undefined to 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.
      • Return Value: Standard useInfiniteQuery result object containing data (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 (not useInfiniteQuery as it’s a single item).
        • queryFn: Calls galleryUsecase.getAttachmentDetail({ attachmentId }).
        • select: A selector function (data) => data?.data?.data to 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.
    • 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, and galleryListingFromSearchAtom from Jotai to adapt its behavior.
      • queryParams are constructed:
        • groupBy: Is set to galleryListingGroupBy if galleryListingFromSearch is true, otherwise defaults to 'all'. This allows search results to potentially have their own grouping independent of the main library view.
        • search: Includes the searchQuery if it’s present.
      • Uses useInfiniteQuery similarly to useAllGalleryQuery, calling galleryUsecase.getFilteredGallery with its constructed queryParams.
      • queryKey: galleryQueryKey.GET_GALLERY_LIST.
    • GitHub Link: packages/app/features/gallery/controller/gallery-listing-query.ts
  • 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)
    }
  • Purpose: This component acts as a presentation controller or a custom hook. It consumes the raw data fetched by useGalleryListingQuery and 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 using useMemo. It flattens the data.pages array from TanStack Query’s paginated result into a single, flat array of gallery items. This is suitable for simple FlatList renderings.
    • sectionsObject: Record<string, GalleryFilterResult[]>: Memoized. This is more complex. It processes data.pages to 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 a SectionList.
    • sections: Memoized. It further transforms sectionsObject into an array format that is directly consumable by React Native’s SectionList component (i.e., [{ title: string, data: any[][] }]). Each item in the data sub-array is also augmented with a sectionId.
  • Exposed Values: Returns an object containing the transformed list, sections, and sectionsObject, along with the original query states from useGalleryListingQuery (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)

  1. User Interaction/Initial Load: A gallery screen mounts or a user interacts (e.g., applies filter, scrolls, searches).
  2. Parameter/State Update:
    • Filter changes: filter-controller updates URL via params-controller.
    • Search: Search input updates librarySearchQueryAtom.
    • Grouping change: LibraryContainer updates libraryGroupByAtom.
  3. Query Trigger: TanStack Query hooks (useAllGalleryQuery, useGalleryListingQuery) react to changes in their dependencies (URL params via useGalleryParams, Jotai atoms).
  4. API Call Construction: The active query hook constructs parameters (filters, page, limit, search terms, group criteria) and calls the appropriate method on galleryUsecase.
  5. Usecase & Repository: galleryUsecase delegates to galleryRepository, which makes the actual HTTP GET request using apiClient to the defined endpoint (e.g., /v1.0/gallery/gallery/filter).
  6. API Response & Caching: The backend API responds with data. TanStack Query caches this data, manages pagination state, and provides status flags (loading, success, error).
  7. 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.
  8. UI Rendering: React components (e.g., GalleryLibraryAll, GalleryLibraryDays, or custom lists using GalleryThumbnail) receive the processed data and render the UI. Loading indicators, empty states, and error messages are also handled based on the query status.
  9. Further Interactions:
    • fetchNextPage is called for infinite scrolling.
    • refetch is 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: A GalleryFilterResult object containing attachmentPath and thumbnailPath.
        • size: Target dimension (likely height or width) for the thumbnail.
        • quality: JPEG quality percentage (default 60).
      • Logic: Prefers thumbnailPath if available, otherwise falls back to attachmentPath. Constructs the URL using envConfig.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.
    • getGalleryImagelUrl(item: GalleryFilterResult): string
      • Logic: Similar to getGalleryThumbnailUrl but omits sizing/quality parameters, intending to fetch the original or a default large version of an image. Uses attachmentPath or thumbnailPath.
      • Return: Full URL for the image.
    • getVideoUrl(item: GalleryFilterResult): string
      • Logic: Specifically uses attachmentPath to construct the URL for a video file, also using envConfig.thumbnailWorkerUrl as the base. This implies the same worker service handles video delivery.
      • Return: Full URL for the video.
  • 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)
    • DownloadBulkAttachmentGalleryParams Interface:
      • 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:
    1. Parameter Processing: Filters out empty criteria and formats date parameters.
    2. API URL Construction: Builds the query string using queryStringify from the processed criteria and appends it to envConfig.galleryDownloadWorkerUrl (base URL for the gallery download worker service).
    3. File Paths: Defines temporary paths for the downloaded ZIP (tempDownloadPath) and its extracted contents (tempExtractPath) using expo-file-system or react-native-fs paths, ensuring platform-compatibility (iOS vs. Android). It also creates necessary temporary “Nimbly” and “attachmentsTemp” directories if they don’t exist.
    4. Cleanup: Calls cleanUp() initially to remove any leftover temporary files from previous attempts.
    5. Token Retrieval: Fetches JWT token using getJwtToken().
    6. File Download (downloadFile internal function):
      • Uses RNFS.downloadFile to download the ZIP from the constructed URL. Passes authorization token in headers. The begin and progress callbacks from args are wired here.
    7. ZIP Extraction (extractZip internal function):
      • Reads the downloaded ZIP file as a Base64 string using FileSystem.readAsStringAsync.
      • Uses JSZip to 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 using uint8ArrayToBase64 (custom Base64 conversion), creates the target directory using RNFS.mkdir, and writes the file using RNFS.writeFile.
    8. Platform-Specific Handling:
      • handleIOSDownload(tempExtractPath): Gets all file paths from the extracted folder. If Sharing.isAvailableAsync() is true, uses Share.open({ urls: files, saveToFiles: true }) from react-native-share to open the iOS share sheet, allowing user to save to Files, etc.
      • handleAndroidDownload(tempExtractPath): Copies the entire extracted folder contents recursively to the public RNFS.DownloadDirectoryPath + '/Nimbly' directory using a custom copyRecursive function (which uses RNFS.copyFile and RNFS.mkdir).
    9. Final Cleanup: Calls cleanUp() again in a finally block to ensure temporary files are removed.
  • Error Handling: Includes a try...catch...finally block. 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.js for date parsing and manipulation.
    • Calculates the difference in days between the input date and 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”.
  • Customization: Takes an optional inputFormat for 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 downloadBulkAttachmentGallery in UI components by managing loading states, progress, and user feedback.
  • Parameters:
    • listData = []: Flat list of gallery items, used for calculateItemsSize.
    • sections = []: Sectioned list of gallery items, also for calculateItemsSize.
    • groupBy = '': Current grouping mode, used to determine how to pass item identifiers to downloadBulkAttachmentGallery.
    • toastExternal = null: Allows providing an external toast controller, otherwise uses its own useToastController().
  • 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:
    1. Resets errorMsg. Validates if ids are provided.
    2. Determines downloadItems (params for downloadBulkAttachmentGallery) based on groupBy:
      • If grouped by ‘months’, it calls getDaysFromGroupByMonths to get relevant day IDs.
      • If grouped by ‘sites’, it calls getSiteIdsFromGroupBySite to get site IDs.
      • Otherwise, it passes the raw ids.
    3. Sets status to ‘zipping’.
    4. Calls downloadBulkAttachmentGallery with downloadItems and wires up onBegin (sets status to ‘downloading’) and onProgress (updates progress state based on bytesWritten and fileSize).
    5. On success, shows a toast message and resets status and progress.
    6. On error, sets status to ‘error’ and stores the error message. Handles a specific cancellation error code (ECANCELLED500).
  • Helper Functions: getSiteIdsFromGroupBySite and getDaysFromGroupByMonths extract relevant identifiers from the sections data structure based on the selected ids.
  • 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.

  • 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.tsx maps 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 in GalleryMenu.tsx).
  • Implementation Details:
    • It renders the <GalleryScreen /> component, which is imported from app/features/gallery/screen (the core feature package).
    • The <GalleryScreen /> itself is a wrapper that primarily renders the main <Gallery /> component (from app/features/gallery/gallery.tsx). This <Gallery /> component is where the GalleryMenu, LibraryContainer, GalleryFooter, etc., are composed to form the complete gallery UI.
    • The entire content is wrapped within <AuthGuard>...</AuthGuard> to ensure user authentication.
  • 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 via expo-router’s parameter hooks (e.g., useLocalSearchParams).
  • Implementation Details:
    • It renders the <GalleryThumbnailDetails /> component, imported from app/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 using AttachmentViewer internally.
    • Includes <Stack.Screen /> from expo-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.
  • 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 from app/features/gallery/filter-option/index.tsx (within the core feature package). This component acts as a container for individual filter controls (e.g., DateFilter, AttachmentTypeFilter from packages/app/features/gallery/components/filters/).
    • Uses <Stack.Screen /> for potential navigator configuration.
    • Wrapped in AuthGuard.
  • 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
  • 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-search if nested. Query parameters for existing filters might be passed to this screen to scope the search.
  • Implementation Details:
    • It renders the <GallerySearchScreen /> component, imported from app/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 using GalleryListingController or a similar mechanism configured for search.
    • Wrapped in AuthGuard.
  • 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)

  1. Entry: User navigates to the Main Gallery Screen (/gallery or /home/gallery). Data is loaded based on default or persisted filters.
  2. 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.
  3. 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.
  4. 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.
  5. 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 PathHTTP MethodPurposeRequest Params (Typical Structure)Response (Brief Structure - Key Fields)Consuming File(s) in Gallery Feature
/v1.0/gallery/gallery/filterGETFetches 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)-iddata: { 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}GETFetches 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/searchGETFetches gallery items based on a search query and other active filters.Query string. E.g., search=keyword&type=video&page=1&limit=30data: GallerySearchStore (similar to GalleryFilteredResponse, tailored for search)gallery-listing-query.ts (when searchQuery is present, via usecase & repo)
{envConfig.thumbnailWorkerUrl}/{mediaPath}GETRetrieves 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}GETDownloads 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)=siteABinary data (ZIP file stream).downloadBulkAttachmentGallery.ts

Notes on API Interactions:

  • apiClient: All calls to the /v1.0/gallery/... endpoints are made through a centralized apiClient which handles base URL configuration, authentication headers (JWT token), and potentially common error handling or interceptors.
  • envConfig URLs:
    • 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 includes id, 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 results in GalleryFilteredResponse can vary: an array if not grouped by the API, or an object where keys are group names (e.g., dates) and values are arrays of GalleryFilterResult if 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 NamePurpose in Gallery Module & SignificanceKey Areas of Usage (Examples)
@my/uiNimbly’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-iconsIcon 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-avExpo 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-systemExpo 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-sharingExpo 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.
jotaiAtomic 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-queryData 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-formForm 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.
solitoCross-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.
jszipZIP 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-fsFile 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-shareCross-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 / momentDate/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-stringURL 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:

    1. Data Acquisition: Data fetching hooks like useAllGalleryQuery or useGalleryListingQuery retrieve a list of GalleryFilterResult objects from the backend. Each object contains metadata like attachmentPath and thumbnailPath.
    2. Component Rendering: A list component (e.g., inside GalleryLibraryAll or search results) iterates over this data, rendering a GalleryThumbnail component for each item.
    3. URL Construction: The GalleryThumbnail component, upon receiving item data, invokes getGalleryThumbnailUrl(item, desiredSize, quality) from packages/app/features/gallery/utils/getGalleryThumbnailURL.ts. This utility constructs a full URL pointing to envConfig.thumbnailWorkerUrl, appending the item’s media path and query parameters for specific dimensions (e.g., h=200&w=200) and compression quality.
    4. Image Rendering: The React Native Image component (or a custom wrapper like @my/ui Image) within GalleryThumbnail uses the generated URL in its source prop. The mobile OS then handles the asynchronous fetching and decoding of the image from this URL.
    5. Video Icon: If item.mediaType is ‘video’, GalleryThumbnail overlays a video icon, indicating the media type without loading the video content itself for the thumbnail.
  • Full View Display (via AttachmentViewer):

    1. User Interaction: The user taps on a GalleryThumbnail. The onPress handler of the thumbnail is triggered.
    2. Navigation: Typically, the onPress handler 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.
    3. Detail Screen Mounting: The detail.tsx screen component mounts. It retrieves the attachmentId from route parameters.
    4. Data Fetching (if needed): The GalleryThumbnailDetails component (rendered by detail.tsx) might use useAttachmentDetailQuery(attachmentId) to fetch comprehensive metadata for this specific attachment if not all data was available from the list item.
    5. Viewer Rendering: GalleryThumbnailDetails then renders the AttachmentViewer component, passing the attachmentURL (either from the fetched detail or constructed using getGalleryImagelUrl or getVideoUrl for consistency) and attachmentType.
    6. Media Display:
      • Image: If attachmentType is ‘photo’, AttachmentViewer uses a React Native Image component to display the full-resolution image from attachmentURL.
      • Video: If attachmentType is ‘video’, AttachmentViewer uses the Video component from expo-av. It sets source={{ uri: attachmentURL }}, enables useNativeControls, and often shouldPlay={true} to begin playback. ResizeMode.CONTAIN ensures 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).
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.

  1. Filter Initiation: The user is on a gallery listing screen (e.g., Main Gallery). They tap the “Filter” icon/button within the GalleryMenu component.
  2. Navigation to Filter UI: The GalleryMenu’s action handler triggers a navigation event (using router.push from Solito) to the dedicated filter options screen (/gallery/filter-option). Current filter parameters (obtained via useGalleryParams) are often passed as route parameters to this screen to pre-fill existing filter selections.
  3. User Filter Selection: On the Filter Options Screen (which renders GalleryFilterOptionScreen), the user interacts with various filter components (e.g., DateFilter, AttachmentTypeFilter, SiteFilter). Each of these components is typically a controlled input, managed by react-hook-form under the FilterFormContext provided by useFilterForm from filter-form-controller.ts. As the user makes selections, the local form state is updated.
  4. Filter Submission: The user confirms their filter choices, usually by tapping an “Apply” or “Done” button on the Filter Options Screen. This action calls the updateFilter function obtained from useFilterController().
  5. URL Parameter Update:
    • The updateFilter function (which is formMethods.handleSubmit(navigateToFilteredScreen)) first triggers react-hook-form’s validation.
    • If validation passes, navigateToFilteredScreen(validatedFilterData) is called.
    • navigateToFilteredScreen (within filter-controller.ts) uses router.push to navigate back to the main gallery listing screen (e.g., /gallery). Crucially, it appends the validatedFilterData object to the URL as query parameters (e.g., /gallery?type=image&startDate=...). This update is facilitated by useUpdateGalleryParams.
  6. Reactive Data Refetch:
    • The main gallery listing screen’s data fetching hook (e.g., useAllGalleryQuery or useGalleryListingQuery) has useGalleryParams() 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.
  7. 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 GalleryListingController process 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-form and displayed within the Filter 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.

  1. Search Initiation: The user taps the search bar/icon within the GalleryMenu on a gallery listing screen or directly starts typing if the search bar is always active.
  2. Navigation to Search Screen (if applicable): Often, activating search navigates the user to a dedicated search screen (/gallery/search or /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.
  3. Query Input: The user types their search terms into the search input field on the search screen.
  4. 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.
  5. Reactive Data Fetching for Search:
    • The useGalleryListingQuery (or a similar hook dedicated to search, like useGallerySearchQuery if galleryUsecase.getGallerySearch is used) on the search screen subscribes to librarySearchQueryAtom.
    • When librarySearchQueryAtom changes (after debouncing), the query hook includes the new search term as a search parameter (and any active filters) in its API request to /v1.0/gallery/gallery/filter or /v1.0/gallery/gallery/search.
  6. 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.
  7. 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 using GalleryThumbnail components. If no results are found, an appropriate “no results” message is shown.
  8. 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.

  1. Item Selection: The user enters “Select Mode” (usually via GalleryMenu). They then tap on multiple GalleryThumbnail components or select entire groups (if the UI supports group selection, e.g., selecting a whole day in LibraryDays view). The IDs of these selected items/groups are collected.
  2. 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 the useGalleryDownload hook.
  3. useGalleryDownload Hook Activation:
    • The hook sets its internal status to ‘zipping’ (or similar initial state) and progress to 0.
    • It determines the exact parameters for downloadBulkAttachmentGallery based on the current groupBy state and the nature of selectedIds (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.
  4. Calling downloadBulkAttachmentGallery Utility: The hook invokes downloadBulkAttachmentGallery with the processed parameters and callbacks for onBegin and onProgress.
  5. API Call for ZIP: downloadBulkAttachmentGallery constructs a URL for envConfig.galleryDownloadWorkerUrl with query parameters specifying the attachments to be included in the ZIP. It makes an authorized GET request.
  6. Streaming Download & Progress:
    • The RNFS.downloadFile function handles the download of the ZIP stream from the API.
    • The onBegin callback (from useGalleryDownload) is triggered, setting status to ‘downloading’.
    • The onProgress callback continuously updates the progress state in useGalleryDownload, which can be used to render a progress bar in the UI.
  7. ZIP Extraction: Once the ZIP file is fully downloaded to a temporary local path, downloadBulkAttachmentGallery uses JSZip to read and extract its contents into another temporary local directory. Each file within the archive is written out.
  8. 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 from react-native-share (or expo-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.
  9. Feedback and Cleanup:
    • Upon successful completion of sharing/saving, useGalleryDownload updates its status to ‘idle’ or ‘success’ and displays a success toast message.
    • If any step fails (network error, file system error, extraction error), status is set to ‘error’, errorMsg is populated, and an error toast is shown.
    • The finally block in downloadBulkAttachmentGallery ensures that all temporary ZIP files and extracted content directories are deleted from the local file system, regardless of success or failure.
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.