docs
/
EventOxygen

Architecture

How the attendee app boots, its two shells, the two-token HTTP client, and the services behind tickets, community and chat.

EventOxygen is the Flutter app at appmint_go/event_app (Dart package event_app, display name EventOxygen). It is small by design: two providers, one HTTP client, one API service, a Socket.IO chat client, and screens that push each other with MaterialPageRoute. There is no named-route table, no offline store, no push notifications and no deep links.

Boot sequence

lib/main.dart:

1. Environment. EnvironmentConfig.setEnvironment(production) under kReleaseMode, otherwise development.

2. Client info. clientInfoService.initialize() snapshots platform, OS, device, app version, language and timezone once.

3. Stripe. StripeService.initialize(); the publishable key is fetched from the server at purchase time, not from the env constants (those are placeholders).

4. runApp(EventApp()). A MultiProvider with exactly AuthProvider and EventProvider, then MaterialApp(title: 'Event App', theme: HaHoTheme.lightTheme, navigatorObservers: [ActivityObserver()], home: AuthWrapper()).

AuthWrapper reads AuthProvider.status: initial and loading show a splash; authenticated shows ChangePasswordScreen when a temporary password must be replaced, else MyEventsScreen; unauthenticated, error and twoFactorRequired show PublicEventsShell.

Shells and navigation

ShellFileRole
PublicEventsShelllib/screens/customer/public_events_shell.dartSigned out. Navy app bar "Events", a "My Account" button that pushes LoginScreen, and EventsScreen as the body. Browsing and buying need no account
EventShelllib/screens/customer/event_shell.dartSigned in and inside one event. Bottom tabs Home (EventHomeScreen), Explore (FeedScreen), a centre + that pushes CreatePostScreen, Inbox (InboxScreen), Me (MeScreen). The drawer lists the user, an event switcher (only when more than one ticketed event), Connections, Meetings, Bookmarks, My Events, then Settings and Help (both currently just close the drawer) and Log out
CustomerShelllib/screens/customer/customer_shell.dartDead code. A five-tab alternative with a QR scanner FAB marked TODO; nothing references it

The shell's Scaffold is keyed by a module-level shellScaffoldKey. Nested tab screens have their own Scaffold, so their menu buttons open the drawer through that key rather than Scaffold.of(ctx), which would resolve to the nested scaffold and open nothing.

Switching events calls EventProvider.selectEvent(id) and jumps to tab 0. Everything inside the shell — feed, people, sessions — is scoped to the active event.

Providers

ProviderOwns
AuthProvider (lib/providers/auth_provider.dart)AuthStatus {initial, loading, authenticated, unauthenticated, error, twoFactorRequired}, the UserModel, 2FA state (challengeToken, twoFactorMethod, pending email and password), requiresPasswordChange; login, verify2FA, register, logout, deleteAccount, forgotPassword, changePassword (auto re-signs in), updateUser. It also pushes the signed-in email into the activity tracker
EventProvider (lib/providers/event_provider.dart)myEvents, myTickets, activeEvent / activeEventId / activeEventData, activeTickets, a ticket-type map; loadMyEvents(), selectEvent(id), clearActiveEvent()

loadMyEvents() is derived, not fetched: it groups GET /client/events/tickets/mine by data.event, fetches each event in parallel, enriches tickets with the event title and ticket-type name and colour, then merges entries by the fetched record's sk — one ticket may reference the event by slug and another by id, and without the merge the drawer listed the same event twice. With exactly one event it auto-selects.

Networking

lib/services/http_client.dart (AppengineHttpClient):

  • Two tokens. An app token from POST /profile/app/key ({appId, secret, key}, header orgid) goes in Authorization: Bearer. The customer's token goes in x-client-authorization: Bearer when a call passes useUserToken: true.
  • Which calls carry the user token matters. Public browsing, event detail, purchase, payment intent, confirm and booking lookup do not. My events and tickets, feed, connections, messages, notifications, bookmarks, follow, meetings and the media library do. The feed must carry it or the server cannot stamp viewerLiked and viewerSaved.
  • On 401 the app renews the app token once and retries.
  • Every request also sends x-client-info (JSON snapshot), x-client-protocol, x-client-timezone, x-client-orgid, orgid, shared-org-id and domainAsOrg. It deliberately omits host and x-client-host so the server resolves the customer-facing host from siteName; sending the API host breaks the deep links in booking emails.
  • Tokens: flutter_secure_storage (access_token, refresh_token); user JSON in SharedPreferences (user_data).

lib/services/api_service.dart is the single API surface: auth, events and tickets, community, media. lib/services/media_upload_service.dart handles chunked multipart uploads (64 KB chunks, 120 s timeout) with a progress notifier.

Real-time chat

lib/services/community_chat_service.dart connects to the Socket.IO namespace <appengineEndpoint>/community-chat, websocket transport only, auth {token: <customer access token>, orgId}. Inbound: message, groupMessage, typing, onlineStatus, error. Outbound: sendMessage, sendGroupMessage, typing, groupTyping, markRead, getOnlineUsers, joinGroup, leaveGroup.

ChatScreen loads history over REST, appends optimistically with sending → sent | failed, sends over the socket when connected and falls back to POST /client/community/messages when not. A dot in the app bar shows socket state; "typing…" clears after 3 s. Images are uploaded through the media service and sent as the message body URL.

Activity tracking

lib/services/activity_tracking_service.dart posts to POST /crm/customer-activity/record: trackScreenView, trackAction, trackSearch, and trackEventAction (importance 5 for ticket_purchase). ActivityObserver is registered as a navigator observer but only fires for named routes, and the app pushes unnamed ones — the explicit trackScreenView calls in each screen's initState do the real work.

Theme

lib/config/haho_theme.dart (cream #F5F3EF, coral #FF6B6B, cyan accent) is the MaterialApp theme; most customer screens style themselves with lib/config/app_colors.dart (navy, orange, teal). lib/config/legal_urls.dart points at https://eventos.app/privacy, /terms and [email protected].

Not implemented

Claim you may seeReality
Push notificationsNone — no FCM, APNs or config. In-app notifications are polled REST records
Deep linksNone — no URL schemes or app links beyond the launcher intent
QR scannerQRProfileScreen renders your code; nothing scans one. The CustomerShell FAB is a TODO
OfflineNo cache; every screen fetches
Card payment on webThe Stripe service is a throwing stub on web; purchase completes only on iOS and Android
Drawer Settings and HelpClose the drawer
Search → People tabA placeholder that falls back to pages