Mobile App Module

The MobileApp module provides the complete mobile app experience — gating, push notifications via Firebase Cloud Messaging HTTP v1 API, device management, voice assistant, and full settings screens for profile, security, roles, activity logs, and more.

Overview

  • Gate: Mobile app only works if this module is enabled on the tenant
  • Push Notifications: FCM HTTP v1 API (production-ready, OAuth2 via service account)
  • Voice Assistant: Long-press FAB to activate, live microphone with waveform visualization
  • Settings Screens: Profile, appearance, notifications, login history, roles, activity logs, team
  • App Version Control: Force update / soft update enforcement
  • Mobile Dashboard: Single aggregated endpoint with card-based UI
  • Dynamic Navigation: Customizable tab bar and FAB quick actions

Architecture

Tech Stack

Layer Technology
Framework Expo SDK 55, React Native 0.83, React 19.2
Router Expo Router v7 (file-based)
Language TypeScript
UI Custom shadcn-style RN components
State React Context (Auth, Theme, Modules, Notifications, Navigation)
Storage AsyncStorage (general), SecureStore (tokens)
Real-time Reverb WebSocket via pusher-js
Push Firebase Cloud Messaging HTTP v1 API

Project Structure

app/
├── app/                        # Expo Router file-based routes
│   ├── _layout.tsx             # Root layout (providers)
│   ├── (auth)/
│   │   └── login.tsx           # Login + 2FA verification
│   └── (app)/
│       ├── _layout.tsx         # Module gate + Stack navigator
│       ├── notifications.tsx   # Notifications screen
│       ├── orders/[id].tsx     # Order detail
│       ├── m/[...slug].tsx     # SDUI catch-all route (dynamic screens)
│       ├── (tabs)/
│       │   ├── _layout.tsx     # Custom tab bar + FAB + voice assistant
│       │   ├── dashboard.tsx   # Card-based dashboard
│       │   ├── orders.tsx      # Order list
│       │   ├── customers.tsx   # Customer list
│       │   ├── warehouse.tsx   # WMS operations
│       │   └── settings.tsx    # Settings hub
│       └── settings/
│           ├── profile.tsx     # Profile management
│           ├── appearance.tsx  # Theme + color mode
│           ├── notifications.tsx # Push preferences
│           ├── login-history.tsx # Login history
│           ├── roles.tsx       # Roles & permissions (admin)
│           ├── activity-logs.tsx # Audit trail (admin)
│           ├── navigation.tsx  # Tab/FAB customization
│           └── team.tsx        # Team management
├── components/
│   ├── screen-header.tsx       # Rounded header with actions
│   ├── voice-assistant.tsx     # Full-screen voice overlay
│   └── ui/                     # Base UI components
├── contexts/
│   ├── auth-context.tsx        # Auth state + permissions
│   ├── theme-context.tsx       # Theme + color mode
│   ├── modules-context.tsx     # Module discovery
│   ├── notifications-context.tsx # Real-time notifications
│   └── navigation-context.tsx  # Dynamic tab/FAB config
├── lib/
│   ├── api/client.ts           # API client (mirrors web)
│   ├── sdui/                   # Server-Driven UI engine
│   │   ├── screen-renderer.tsx # Core renderer
│   │   ├── section-registry.ts # Component registry
│   │   ├── action-handler.ts   # Action execution
│   │   ├── field-renderer.tsx  # Data field rendering
│   │   ├── cache.ts            # Two-layer cache
│   │   ├── event-bus.ts        # Cross-component events
│   │   └── sections/           # 14 section components
│   ├── notifications/push.ts  # Push token + FCM registration
│   ├── echo.ts                 # Reverb WebSocket setup
│   └── storage/                # SecureStore + AsyncStorage
├── theme/
│   ├── shadcn/                 # Default theme
│   └── modern/                 # Vibrant indigo theme
└── google-services.json        # Firebase config (Android)

Server-Driven UI (SDUI)

The mobile app supports dynamic module screens via Server-Driven UI. Any module can contribute mobile screens by defining them as JSON on the backend — no app rebuild required.

  • Catch-all route: /(app)/m/[...slug] renders any SDUI screen
  • Sidebar integration: Dynamic screens appear in the app sidebar automatically
  • 14 section types: stats-row, list, grid, detail-fields, form, chart, timeline, actions-bar, tabs, and more
  • Search, filters, pagination: Built into the list section component
  • Actions: navigate, API calls, share, copy, modals, sheets — all driven by JSON

See the full documentation:

SDUI API Endpoints

Endpoint Description
GET /mobile/screens List all registered screens
GET /mobile/screens/manifest Versioned manifest for cache sync
GET /mobile/screens/{id} Full SDUI JSON definition
GET /mobile/screens/{id}/data/{section} Section data (paginated, filterable)
POST /mobile/screens/{id}/action/{actionId} Execute server-side action

How the Gate Works

  1. User logs in via /api/v1/auth/login
  2. App calls GET /api/v1/mobile/check (public, no auth required)
  3. If installed: false → slide-up "Mobile Access Not Available" screen with setup instructions
  4. If installed: true → proceeds to main app
  5. The gate also displays license notice (mobile access requires separate license)

Custom Tab Bar

The bottom tab bar is fully custom with:

  • Rounded top corners (20px) with card background
  • Center FAB that floats above the bar
  • Dynamic tabs — configured via backend API, user-customizable
  • FAB arc menu — tap opens animated arc fan of quick actions
  • Voice assistant — long-press FAB (400ms) activates voice overlay

FAB Icon Animation

The FAB cycles through 3 icons with smooth crossfade:

  • Plus (3s idle) → Mic (1.5s) → Sparkles (1.5s) → back to Plus

This hints at the dual functionality (menu + voice) without adding UI clutter.

Voice Assistant

Activated by long-pressing the center FAB button.

Flow

  1. Listening — live microphone input with animated waveform
  2. Processing — typing dots animation
  3. Response — animated text with mock response (AI integration ready)

Audio Processing

Feature Implementation
Recording expo-av Audio.Recording with metering (dev build)
Noise gate -35dB threshold — ambient noise filtered
Smoothing Exponential moving average (factor 0.35)
Silence detection Below 0.05 normalized = zero
Sample rate 16kHz mono (voice-optimized)
Fallback Simulated waveform in Expo Go

Waveform Visualization

  • 28 bars updated every 60ms from mic metering
  • Bars shift left like a scrolling waveform
  • Center bars slightly taller (natural wave shape)
  • Per-bar jitter for organic feel
  • Opacity scales with intensity

Push Notifications

Architecture

NotificationService::send()
    ├── Creates Notification record (tenant DB)
    ├── Broadcasts NotificationCreated event (WebSocket → real-time)
    └── SendPushOnNotification listener (queued)
            └── PushNotificationService::sendToUser()
                    └── ExpoPushClient::send()
                            ├── ExponentPushToken → Expo Push API
                            └── FCM token → FCM HTTP v1 API

FCM HTTP v1 API (Production)

The module uses the modern FCM HTTP v1 API with OAuth2 — not the deprecated legacy server key API.

Component Detail
Endpoint POST https://fcm.googleapis.com/v1/projects/{projectId}/messages:send
Auth OAuth2 Bearer token via service account JWT
Token cache 1 hour (auto-refreshes via Laravel Cache)
JWT library firebase/php-jwt (bundled with Laravel Passport)
Service account storage/app/firebase-service-account.json

Token Auto-Routing

ExpoPushClient::send() automatically detects the token format:

ExponentPushToken[...] → Expo Push API (https://exp.host/--/api/v2/push/send)
Raw FCM token          → FCM v1 API (https://fcm.googleapis.com/v1/projects/.../messages:send)

Notification Preferences

Users can toggle push categories per-device:

Category Type Description
Orders order_update New orders, status changes, cancellations
Inventory inventory_alert Low stock warnings, restock reminders
Team team_notification Member joins, role changes, invitations
Custom custom Admin broadcasts, system updates

Auto-Push Triggers

Every notification created via NotificationService::send() automatically triggers a push via the SendPushOnNotification event listener. This covers:

  • Order created/updated/shipped/delivered
  • Low stock alerts
  • Team member events
  • Admin broadcasts
  • Any custom notification

Firebase Setup (Android)

  1. Create project at Firebase Console
  2. Add Android app with package name com.anonymous.autocom
  3. Download google-services.json → place at app/google-services.json
  4. Generate service account key (Project Settings → Service accounts → Generate new private key)
  5. Place at storage/app/firebase-service-account.json in the backend

Push Notification Log

All push attempts are logged in push_notification_log:

Field Description
status pending, sent, failed
error Error message if failed
type order_update, inventory_alert, team_notification, custom

Settings Screens

Profile (settings/profile)

  • Avatar — tap to pick from gallery (expo-image-picker, 1:1 crop), uploads via POST /profile/avatar
  • Name — editable with save button
  • Email — read-only display
  • Password change — expandable section with current/new/confirm fields, show/hide toggles, match indicator
  • 2FA status — shows enabled/disabled with backup codes count
  • Active sessions — lists all OAuth tokens with device name, last active time, revoke per-session or all

Appearance (settings/appearance)

  • Color mode — Light/Dark/System cards with icon, label, description, check indicator
  • Theme picker — large cards per theme with:
    • Mini UI mockup (tiny replica of the app using that theme's actual color tokens)
    • Color swatches (Primary, Accent, BG, Card, Text, Border, Alert)
    • Active pill badge + border highlight

Notifications (settings/notifications)

  • Status card — Push (active/off), Realtime (connected/off), Unread count
  • Push preferences — 4 toggle switches (Orders, Inventory, Team, Custom)
  • Graceful fallbacks — info cards when push unavailable (Expo Go) or disabled (no permission)
  • Mark all as read action

Login History (settings/login-history)

  • Stats row — sign-ins, failed attempts, flagged, unique IPs (last 30 days)
  • Filter pills — All / Success / Failed / Blocked
  • Entry list — status dot, device icon, browser + platform, IP, failure reason, relative time
  • Pagination — infinite scroll with pull-to-refresh
  • APIGET /login-history with status/date filters

Roles & Permissions (settings/roles) — Admin Only

Requires team.manage_roles permission.

  • Role list — cards with shield icon, name, permission count, system badge
  • View mode — role details, permission groups (collapsible), edit/delete actions
  • Edit/Create mode — name/description inputs, permission toggles per group with Select All, sensitive permission warnings
  • Constraints — system roles (owner, admin, manager, support, viewer) are read-only

Activity Logs (settings/activity-logs) — Admin Only

Requires admin.audit_log permission.

  • 3 tabs — All / Security / Team (each hits dedicated endpoint)
  • Entry list — colored action icon, action label, user name, IP, relative time
  • 21 action types mapped with distinct icons/colors
  • Pagination — infinite scroll

Team (settings/team)

  • Team member management (view, invite, suspend, remove)
  • Role assignment

API Client Methods

Profile & Auth

Method Endpoint Description
updateProfile({name, email}) PATCH /profile Update name
updatePassword({...}) POST /auth/change-password Change password (revokes other sessions)
uploadAvatar(uri) POST /profile/avatar FormData image upload
getSessions() GET /profile/sessions List active OAuth tokens
revokeSession(id) DELETE /profile/sessions/{id} End specific session
revokeAllSessions() DELETE /profile/sessions End all other sessions

2FA

Method Endpoint
get2FAStatus() GET /2fa/status
enable2FA(password) POST /2fa/enable
confirm2FA(code) POST /2fa/confirm
disable2FA(password, code) POST /2fa/disable

Login History

Method Endpoint
getLoginHistory({status, per_page, page}) GET /login-history
getLoginHistoryStats() GET /login-history/stats

Activity Logs

Method Endpoint
getActivityLogs({action, per_page, page}) GET /activity-logs
getSecurityEvents({per_page, page}) GET /activity-logs/security
getTeamEvents({per_page, page}) GET /activity-logs/team

Roles & Permissions

Method Endpoint
getRoles() GET /team/roles
getRole(id) GET /team/roles/{id}
createRole({name, description, permissions}) POST /team/roles
updateRole(id, {...}) PUT /team/roles/{id}
deleteRole(id) DELETE /team/roles/{id}
getPermissions() GET /team/permissions

Notifications

Method Endpoint
getNotifications({...}) GET /notifications
getUnreadNotificationCount() GET /notifications/unread-count
markNotificationAsRead(id) PATCH /notifications/{id}/read
markAllNotificationsAsRead() POST /notifications/mark-all-read

Mobile-Specific

Method Endpoint Description
checkMobileModule() GET /mobile/check Public module check
POST /mobile/devices Register device FCM token + device info
DELETE /mobile/devices/{token} Unregister device Called on logout
PATCH /mobile/devices/preferences Update push preferences Per-type toggles
POST /mobile/devices/heartbeat Device heartbeat Periodic keep-alive
GET /mobile/dashboard Aggregated dashboard Overview + stats + orders

Configuration

Backend (config/mobile-app.php)

return [
    'push' => ['enabled' => env('MOBILE_PUSH_ENABLED', true)],
    'fcm_service_account' => env('FCM_SERVICE_ACCOUNT_PATH', storage_path('app/firebase-service-account.json')),
    'version_check' => ['enabled' => env('MOBILE_VERSION_CHECK_ENABLED', true)],
    'dashboard_cache_ttl' => env('MOBILE_DASHBOARD_CACHE_TTL', 300),
    'max_devices_per_user' => env('MOBILE_MAX_DEVICES_PER_USER', 5),
    'cleanup_inactive_days' => env('MOBILE_CLEANUP_INACTIVE_DAYS', 90),
    'default_tabs' => ['dashboard', 'orders', 'warehouse', 'settings'],
    'default_quick_actions' => [...],
    'available_mobile_tabs' => [...],
];

Mobile App (app/.env)

EXPO_PUBLIC_API_URL=http://your-api:5350/api/v1
EXPO_PUBLIC_REVERB_APP_KEY=your_reverb_key
EXPO_PUBLIC_REVERB_HOST=your-api-host
EXPO_PUBLIC_REVERB_PORT=5352
EXPO_PUBLIC_REVERB_SCHEME=http

Android Permissions (app.json)

{
  "android": {
    "permissions": ["RECORD_AUDIO", "READ_MEDIA_IMAGES", "android.permission.CAMERA"],
    "googleServicesFile": "./google-services.json"
  },
  "ios": {
    "infoPlist": {
      "NSMicrophoneUsageDescription": "AutoCom uses your microphone for the AI voice assistant.",
      "NSPhotoLibraryUsageDescription": "AutoCom needs access to your photos to update your profile picture."
    }
  }
}

Backend Services

Service Responsibility
ExpoPushClient Push delivery — auto-routes Expo tokens vs FCM tokens, FCM v1 OAuth2
PushNotificationService Push orchestration, user targeting, preference filtering, stats
SendPushOnNotification Event listener — bridges NotificationCreated → push automatically
DeviceService Device registration, cleanup, max device enforcement
MobileDashboardService Aggregated dashboard with caching
NavigationService Dynamic tab bar and quick action configuration
MobileScreenRegistry SDUI screen registration, definition serving, action execution
AppVersionService Version CRUD, version comparison

Building the App

Prerequisites

  • Node.js 18+
  • JDK 17 (Azul Zulu recommended: brew install --cask zulu@17)
  • Android SDK (platform-tools, build-tools 35+, SDK platform 35+)
  • ANDROID_HOME and JAVA_HOME environment variables set

Development Build (Android)

cd app
npm install
npx expo prebuild --platform android
npx expo run:android    # builds native + starts Metro

Development Build (iOS)

cd app
npm install
npx expo prebuild --platform ios
npx expo run:ios

Dev Server Only (after first build)

cd app
npx expo start --dev-client

Important Notes

  • First build takes 10-15 minutes (downloads NDK, compiles native modules)
  • Subsequent builds are fast (~30s incremental)
  • android/ and ios/ directories are gitignored — regenerated via npx expo prebuild
  • google-services.json must be in app/ root (Expo copies it during prebuild)
  • Firebase service account JSON must be in backend storage/app/ (not committed — deploy secret)

Artisan Commands

Command Description
mobile:cleanup-devices --days=90 Deactivate devices inactive for N days

Deployment Checklist

  1. Firebase project created with Android app registered
  2. google-services.json in app/ root
  3. Firebase service account JSON in backend storage/app/firebase-service-account.json
  4. MOBILE_PUSH_ENABLED=true in backend .env
  5. Queue worker running (php artisan horizon or queue:work) for push job processing
  6. Reverb WebSocket server running for real-time notifications
  7. Mobile App module enabled on tenant via admin panel