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
| Shell | File | Role |
|---|---|---|
PublicEventsShell | lib/screens/customer/public_events_shell.dart | Signed out. Navy app bar "Events", a "My Account" button that pushes LoginScreen, and EventsScreen as the body. Browsing and buying need no account |
EventShell | lib/screens/customer/event_shell.dart | Signed 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 |
CustomerShell | lib/screens/customer/customer_shell.dart | Dead 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
| Provider | Owns |
|---|---|
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}, headerorgid) goes inAuthorization: Bearer. The customer's token goes inx-client-authorization: Bearerwhen a call passesuseUserToken: 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
viewerLikedandviewerSaved. - 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-idanddomainAsOrg. It deliberately omitshostandx-client-hostso the server resolves the customer-facing host fromsiteName; sending the API host breaks the deep links in booking emails. - Tokens:
flutter_secure_storage(access_token,refresh_token); user JSON inSharedPreferences(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 see | Reality |
|---|---|
| Push notifications | None — no FCM, APNs or config. In-app notifications are polled REST records |
| Deep links | None — no URL schemes or app links beyond the launcher intent |
| QR scanner | QRProfileScreen renders your code; nothing scans one. The CustomerShell FAB is a TODO |
| Offline | No cache; every screen fetches |
| Card payment on web | The Stripe service is a throwing stub on web; purchase completes only on iOS and Android |
| Drawer Settings and Help | Close the drawer |
| Search → People tab | A placeholder that falls back to pages |