Documentation

appmint_mobile — operator app

Walkthrough of appmint_mobile — the staff-facing admin app, the most polished reference for operator workflows.

appmint_mobile is the operator-side admin app. Where the consumer apps target end-users, this one is for staff: the people running a tenant. It's the most thoroughly documented of the four reference apps and the canonical source for admin mobile patterns. Repo: /Users/imzee/projects/appmint_go/appmint_mobile/.

What it uses

ModuleEndpoints
Profile (staff)/profile/user/signin, /profile/user/refresh, /profile/user/magic-link, /profile/user/self
CRM/crm/leads/detail, /crm/activity/*, /crm/inbox/*, /crm/tickets/*, /crm/reservations/*
Repository/repository/find/*, /repository/update/*, /repository/search
Chat/chat Socket.IO (operator side — pick-next, transfer, takeover)
Client account/client-account/notifications/push-token
Broadcast / messaging/crm/inbox/update?send=true for outbound email/SMS

The app spans CRM, support inbox, customer chat, reservations, and tickets — the full operator daily-driver surface.

What's different from the consumer apps

AspectConsumer appsappmint_mobile
Auth endpoint/profile/customer/signin/profile/user/signin
HTTP packageshttp onlyhttp + dio
StateProviderProvider + flutter_bloc
Storagesecure_storage + shared_prefssecure_storage + shared_prefs + Hive
BiometricsNonelocal_auth with cold-start gate
TelemetryNonedevice_info_plus, package_info_plus
Voice/PhoneNonetwilio_voice for inbound + outbound calls
ScannerNonemobile_scanner for QR/barcode

These additions make the operator app heavier but give it the offline tolerance, biometric gate, and call/scan capabilities operators expect.

Project layout

appmint_mobile/lib/
├── config/
│   ├── environment.dart        # Dev vs prod base URLs
│   ├── app_config.dart         # Re-exports from environment
│   └── theme.dart              # Material 3 theme
├── services/
│   ├── http_client.dart        # Singleton AppmintHttpClient
│   ├── api_service.dart        # CRM + auth wrappers
│   ├── base_data_service.dart  # Generic /repository/* CRUD
│   ├── repository_service.dart # Typed repository helpers
│   ├── chat_service.dart       # /chat WebSocket (operator side)
│   ├── chat_presence_service.dart
│   ├── communications_service.dart # Inbox, tickets, messaging
│   ├── customer_activity_service.dart
│   ├── events_service.dart
│   ├── file_service.dart       # Multipart uploads
│   ├── softphone_service.dart  # Twilio Voice integration
│   ├── client_info_service.dart # Telemetry / heartbeats
│   └── storage_service.dart
├── providers/                  # AuthProvider, LeadsProvider, ...
├── screens/
│   ├── auth/                   # Login, register, magic link
│   ├── home/                   # Dashboard
│   ├── leads/                  # Lead CRUD + detail
│   └── profile/
└── widgets/

Files to read first

For anyone forking the operator app:

  • lib/services/http_client.dart — the canonical singleton, with refresh callback and force-logout. Same shape that's documented in client-pattern, word for word.
  • lib/services/api_service.dart — every CRM endpoint wrapped in a typed method. Read it for the catalog of operator calls.
  • lib/services/chat_service.dart — the operator-side WebSocket: pick-next from queue, transfer chat, takeover from AI, archive. Far richer than the consumer chat service.
  • lib/services/softphone_service.dart — Twilio Voice integration with CallKit on iOS and a native call screen on Android. If you need inbound voice calls in your operator app, this is the pattern.
  • README.md and ENVIRONMENT_CONFIG.md in the repo root — describe the auth model, environment switching, and feature flags the app uses.

Patterns worth lifting

Hive offline cache

Lead lists, customer lists, and inbox messages are cached in Hive boxes. The pattern documented in offline-cache is from this app. Operators flipping between Leads → Inbox → Dashboard see instant tab transitions because everything renders from cache and refetches in the background.

Biometric gate on cold start

if (auth.isAuthenticated && await biometric.isEnabled()) {
  final ok = await biometric.authenticate(reason: 'Sign in to AppMint');
  if (!ok) await auth.signOut();
}

Sensitive screens (delete account, payouts, customer PII) re-prompt biometrics inline. See biometrics.

Twilio inbound + outbound calls

Operators take customer calls without leaving the app. The softphone_service.dart registers a Twilio device, hooks CallKit on iOS, and shows incoming-call UI even when the app is backgrounded. Outbound dialing routes through the same service for consistency.

This is a significant feature with subtle platform setup — read the file plus the Twilio Voice plugin docs together.

Heartbeat / presence

client_info_service.dart collects device_info_plus data (model, OS, app version) and emits a heartbeat to the server every 30 seconds while the app is active. The server uses this for presence (who's online) and telemetry. The same heartbeat pattern works for any operator app.

dio for streaming uploads

The app uses dio for multipart uploads with progress callbacks; everything else stays on http. Both share the same auth header and base URL — they just emit on the singleton's getters. Don't pick one or the other globally; let the type of operation pick.

What it shows about admin workflows

The README outlines the operator daily-driver surface:

  • Authentication: email/password + magic link, JWT refresh, secure storage, biometric gate.
  • Lead management: paginated list, create/update/delete, lead detail, pull-to-refresh.
  • Dashboard: KPI cards, welcome card, quick actions.
  • Profile management: settings, theme toggle, account deletion (Apple-mandated).

The admin-mobile section of these docs goes into each in depth — see Admin mobile overview.

Where to fork from

If you're building any operator/admin app on top of AppMint:

  1. Start from this codebase rather than greenfield. The plumbing alone is 5-10 days of work.
  2. Replace the leads-centric screens with whatever your operators do.
  3. Keep biometrics, offline cache, and the heartbeat — they're table-stakes for an operator app.

Reference docs to pair with this example