# Floating Banner System ## Overview The Floating Banner system provides a comprehensive solution for displaying temporary notifications, alerts, and messages in the Mattermost mobile application. It consists of multiple components working together to deliver a smooth, accessible, and highly customizable banner experience. ## System Architecture ```mermaid graph TB subgraph "Application Layer" NC[NetworkConnectivityManager] APP[App Components] INIT[App Initialization] end subgraph "Banner Management Layer" BM[BannerManager
Singleton] end subgraph "UI Layer" OVL[Navigation Overlay
System] FB[FloatingBanner
Component] B[Banner
Component] BI[BannerItem
Component] CB[ConnectionBanner
Component] end subgraph "Core Components" GH[GestureHandler] RA[React Native
Reanimated] SA[Safe Area] end NC -->|Network Events| BM APP -->|Show/Hide Requests| BM INIT -->|System Setup| BM BM -->|Overlay Management| OVL OVL -->|Render| FB FB -->|Position & Layout| B B -->|Content| BI B -->|Custom Content| CB B -->|Animations| RA B -->|Gestures| GH B -->|Layout| SA style BM fill:#e1f5fe style FB fill:#f3e5f5 style B fill:#e8f5e8 ``` ## Component Architecture ### 1. BannerManager (Singleton) **Purpose**: Central controller for banner lifecycle management ```mermaid stateDiagram-v2 [*] --> Hidden: Initial State Hidden --> Showing: showBanner() Showing --> AutoHiding: showBannerWithAutoHide() Showing --> Hidden: hideBanner(bannerId) / User Dismiss AutoHiding --> Hidden: Timeout Expires AutoHiding --> Hidden: hideBanner(bannerId) / User Dismiss Hidden --> [*]: cleanup() Showing --> [*]: cleanup() AutoHiding --> [*]: cleanup() ``` **Key Features**: - Singleton pattern with support for multiple stacked banners in a single overlay - Per-banner auto-hide timers for independent timeout management - **Promise-chain queue system** to prevent race conditions (replaces previous UpdateState enum) - 2-second overlay dismiss delay to prevent flickering during rapid banner changes - Overlay system integration with `Navigation.updateProps` for efficient updates - Error handling for dismiss callbacks - State tracking (active banners array, overlay visibility, individual timers) - **Required `bannerId` parameter** for `hideBanner()` to prevent accidental cross-system interference **Android Limitation**: - On Android, when both top AND bottom banners are displayed simultaneously, only ONE GestureHandlerRootView can properly register touch events - iOS works correctly with simultaneous top/bottom banners - Workaround not yet implemented - future enhancement may add banner position prioritization on Android ### 2. FloatingBanner Component **Purpose**: Main rendering component that splits banners by position and delegates to BannerSection ```mermaid flowchart TD FB[FloatingBanner] --> CHECK{Banners exist?} CHECK -->|No| NULL[Return null] CHECK -->|Yes| SPLIT[Split by position] SPLIT --> TOP[Top Banners] SPLIT --> BOTTOM[Bottom Banners] TOP --> BSTOP[BannerSection 'top'] BOTTOM --> BSBOTTOM[BannerSection 'bottom'] BSTOP --> GHRTOP[GestureHandlerRootView
positioned at top] BSBOTTOM --> GHRBOTTOM[GestureHandlerRootView
positioned at bottom] GHRTOP --> ABTOP[AnimatedBannerItem
Components] GHRBOTTOM --> ABBOTTOM[AnimatedBannerItem
Components] ABTOP --> CONTENT[Banner Content] ABBOTTOM --> CONTENT CONTENT --> BI[BannerItem] CONTENT --> CUSTOM[Custom Content] ``` ### 3. BannerSection Component **Purpose**: Positions and sizes the GestureHandlerRootView for a banner section (top or bottom) **Key Features**: - Creates a `GestureHandlerRootView` for each section (top/bottom) - Uses `useBannerGestureRootPosition` hook for platform-specific positioning - Calculates container height based on number of banners - Applies safe area insets for top banners - Handles keyboard adjustments (iOS only) - Returns `null` when no banners in section ```mermaid graph LR subgraph "BannerSection Features" POS["Position Calculation
• useBannerGestureRootPosition hook
• Container height calculation
• Safe area insets (top only)
• Keyboard-aware (iOS)"] GESTURE["Gesture Root
• GestureHandlerRootView
• pointerEvents='box-none'
• Positioned absolutely"] RENDER["Banner Rendering
• AnimatedBannerItem per banner
• Swipe to dismiss gestures
• Stacked with spacing"] end POS --> GESTURE GESTURE --> RENDER ``` ### 4. useBannerGestureRootPosition Hook **Purpose**: Calculates positioning and sizing for GestureHandlerRootView based on platform, device type, and keyboard state **Key Features**: - Platform-specific bottom offsets (Android vs iOS) - Tablet-specific width constraints (96% of available width after sidebar) - Keyboard-aware positioning (iOS dynamically adjusts, Android uses fixed offset) - Memoized for performance - Exports `BANNER_TABLET_WIDTH_PERCENTAGE` constant via `testExports` ## Data Flow ### Banner Lifecycle ```mermaid sequenceDiagram participant App as Application Code participant BM as BannerManager participant Overlay as Navigation Overlay participant FB as FloatingBanner participant B as Banner participant User as User Interaction App->>BM: showBanner(config) BM->>BM: Add to activeBanners[] BM->>BM: updateOverlay() alt First Banner (overlay not visible) BM->>Overlay: showOverlay(FLOATING_BANNER) BM->>BM: overlayVisible = true else Additional Banner (overlay exists) BM->>Overlay: Navigation.updateProps(banners) end Overlay->>FB: Render with banners array FB->>FB: Split banners by position FB->>B: Render individual banners with offset B->>B: Calculate positioning B->>B: Apply animations User->>B: Swipe to dismiss B->>BM: handleDismiss(bannerId) BM->>BM: Remove from activeBanners[] BM->>BM: Clear banner's timer alt Last banner removed BM->>Overlay: Navigation.updateProps(empty array) Note over BM,Overlay: UI clears immediately BM->>BM: Wait 2s (dismiss delay) Note over BM: Prevents flickering on rapid changes BM->>Overlay: dismissOverlay() BM->>BM: overlayVisible = false else Other banners remain BM->>Overlay: Navigation.updateProps(remaining banners) end ``` ### Network Connectivity Integration ```mermaid sequenceDiagram participant NCM as NetworkConnectivityManager participant BM as BannerManager participant CB as ConnectionBanner NCM->>NCM: Network state change NCM->>NCM: updateBanner() alt Disconnected State NCM->>BM: showBanner(disconnected config) BM->>CB: Render disconnected banner else Performance Issues NCM->>BM: showBanner(performance config) BM->>CB: Render performance banner else Connected NCM->>BM: hideBanner() end ``` ## API Reference ### BannerConfig Interface ```typescript interface BannerConfig { id: string; // Unique identifier title: string; // Banner title text message: string; // Banner message text type?: 'info' | 'success' | 'warning' | 'error'; // Visual styling dismissible?: boolean; // Can user dismiss (default: true) autoHideDuration?: number; // Auto-hide timeout in ms position?: 'top' | 'bottom'; // Screen position (default: 'top') onPress?: () => void; // Tap handler onDismiss?: () => void; // Dismiss handler customComponent?: ReactNode; // Custom banner component } ``` ### BannerManager API ```typescript class BannerManager { // Show banner immediately (stacks with existing banners) showBanner(config: BannerConfig): void; // Show banner with auto-hide (per-banner timer) showBannerWithAutoHide(config: BannerConfig, durationMs?: number): void; // Hide specific banner by ID (required to prevent accidental cross-system interference) hideBanner(bannerId: string): void; // Hide all banners at once hideAllBanners(): void; // Clean up all timeouts and state cleanup(): void; // Get most recently added banner ID getCurrentBannerId(): string | null; // Check if any banner is visible isBannerVisible(): boolean; } ``` ### Internal State Management ```typescript private activeBanners: FloatingBannerConfig[] = []; // Array of active banners private overlayVisible = false; // Tracks overlay state private autoHideTimers: Map = new Map(); // Per-banner timers private updateQueue: Promise = Promise.resolve(); // Promise-chain queue private dismissOverlayTimer: NodeJS.Timeout | null = null; // 2s dismiss delay private dismissOverlayResolve: (() => void) | null = null; // Delay cancellation ``` **Note**: The previous `UpdateState` enum has been replaced with a promise-chain queue system for better handling of concurrent updates. ## Usage Patterns ### 1. Basic Banner Display ```typescript import {BannerManager} from '@managers/banner_manager'; // Simple info banner BannerManager.showBanner({ id: 'welcome-message', title: 'Welcome!', message: 'Thanks for using Mattermost', type: 'success' }); ``` ### 2. Auto-hiding Banner ```typescript // Show banner for 3 seconds (with per-banner timer) BannerManager.showBannerWithAutoHide({ id: 'temp-notification', title: 'Message Sent', message: 'Your message was delivered successfully', type: 'success' }, 3000); // Multiple auto-hide banners work independently BannerManager.showBannerWithAutoHide({ id: 'banner-1', title: 'First', message: 'Auto-hides in 5s', type: 'info' }, 5000); BannerManager.showBannerWithAutoHide({ id: 'banner-2', title: 'Second', message: 'Auto-hides in 3s', type: 'success' }, 3000); // Both banners stack and auto-hide independently ``` ### 3. Network Status Banner ```typescript // In NetworkConnectivityManager private showDisconnectedBanner() { BannerManager.showBanner({ id: 'network-disconnected', title: 'No Connection', message: 'Check your internet connection', type: 'error', position: 'bottom', customComponent: ( this.handleBannerDismiss()} /> ) }); } ``` ### 4. Custom Component Banner ```typescript // Custom banner with complex content BannerManager.showBanner({ id: 'custom-banner', title: 'Custom', message: 'Custom message', customComponent: ( Custom banner content