Software Design Description (SDD) for EV Power Mobile Application EV Power Mobile Application SDD
← View SRS
Official Engineering Design Document • IEEE Std 1016-2009

Software Design Description (SDD)
for EV Power Mobile Application

Architectural Blueprints, Component Hierarchies, State Management, and Low-Level Coding Design for the EV Power Mobile Client across Vietnam

1. Introduction & Design Overview

1.1 Purpose & Objectives

This Software Design Description (SDD) establishes the definitive, production-grade architectural and low-level coding specification for the EV Power Mobile Application (repository evp-app, application identifier com.evpower.chargingapp). While the companion Software Requirements Specification (SRS) (docs/SRS.html, IEEE Std 830-1998) details what the application does from a functional and business perspective, this SDD defines how the software is architected, decomposed, structured, and implemented in code adhering to IEEE Std 1016-2009.

The objectives of this design document are:

  • To formalize the Clean Layered Architecture governing the separation between Presentation, State Management, Domain Hooks, API Transport, and Native Persistence.
  • To establish the System Context Viewpoint and external actor boundaries spanning EV Drivers, CPMS Cloud Microservices, Physical EVSE Hardware, VNPay Payment Gateway, and Push Notification Providers.
  • To document the Zustand client state (6 modular stores) and TanStack React Query server caching lifecycles, including cache invalidation, background reconciliation, and multi-session keying.
  • To specify the Dual-Instance Axios networking pipeline, dynamic microservice path routing, and the thread-safe 401 Refresh Token Mutex Queue with rotatedByOtherFlow race recovery.
  • To provide low-level coding blueprints for the 5-step charging state machine, including real-time reanimated car silhouette liquid wave fill (buildWavePath), hardware stabilization delays, explicit driver CTA confirmation, and the AC charger battery SoC suppression fallback (BR-CHG-06).
  • To serve as the canonical architectural guide for mobile engineers maintaining, extending, or refactoring the EV Power codebase on Expo SDK 54 and React Native 0.81 New Architecture.

1.2 System Context Viewpoint & External Actor Boundaries

In compliance with IEEE Std 1016-2009 (Context Viewpoint), the EV Power Mobile Application operates as a distributed edge client interacting with multiple external actors and enterprise systems:

EV DRIVER (User) iOS / Android Device Touch • Camera • Bio Auth PHYSICAL EVSE & RFID Physical Charging Post RFID Tap • QR Code Decal Connector Relay Closure SYSTEM BOUNDARY: EVP MOBILE APP com.evpower.chargingapp • Expo SDK 54 • React Native 0.81 Auth & Biometrics SecureStore AES-256 Station Map & Search Leaflet Web • Location Charging Orchestrator 5-Step State Machine Multi-Session Keying EVP Wallet & Ledger 1 EVP = 1,000 VND Embedded VNPay WebView RFID Point Vouchers WalletServices /cards VAT Invoice & Fleet Binary PDF /download NETWORKING & SECURITY INFRASTRUCTURE Dual-Instance Axios • 401 Refresh Mutex • rotatedByOtherFlow NetInfo Reachability • TanStack Query Cache • Session Expired Guard CPMS CLOUD PLATFORM RESTful Microservices auth, stations, charging, wallet, invoices, vehicles, notifications PAYMENT GATEWAY VNPay Sandbox / Prod In-App WebView • IPN Webhook PUSH NOTIFICATION SERVICE Expo Push Service / APNs / FCM POST /api/v1/notifications/devices HARDWARE CHARGER FLEET OCPP 1.6J / 2.0.1 Protocol Managed by CPMS Cloud QR Scan HTTPS/REST WebView Push APNs OCPP 1.6J
Fig 1.1: IEEE Std 1016 System Context Viewpoint & External Actor Boundary Interactions
External Actor / System Interface Protocol Interaction Boundaries & Data Exchanged
EV Driver (End User) React Native UI • Native Gestures Touch inputs, camera QR scanning, biometrics, live telemetry observation, and swipe-to-stop session control.
CPMS Cloud Microservices HTTPS / REST / JSON • JWT Bearer Dynamic routing across auth, stations, charging, wallet, invoices, vehicles, notifications.
Physical EVSE Fleet (Chargers) OCPP 1.6J / 2.0.1 (via CPMS) • QR Decal • RFID Physical plug sensing, contactor relay energization, hardware connector status (Available, Preparing, Charging, Finishing).
VNPay Payment Gateway Embedded <TopupPaymentWebView /> • IPN Webhook Driver initiates wallet top-up; app loads secure VNPay checkout URL; backend receives IPN webhook and triggers push alert.
Push Notification Providers Expo Push Service • APNs • FCM Transmits background push payloads (ORDER_CREATED, CHARGING_STOPPED, TOPUP_SUCCESS) for reactive UI state synchronization.
Mobile OS Native Services Expo Native Modules • iOS / Android SDK Hardware Keychain / KeyStore (SecureStore AES-256), NetInfo cellular/wifi observer, FileSystem binary PDF caching, and Sharing sheet.

1.3 Scope of Software Design

The scope of this document encompasses all software modules residing within the evp-app repository:

Subsystem / Scope Area Target Directory Architectural Responsibility
Routing & Screens app/, screens/ File-based navigation (Expo Router 54), nested tabs, modal sheets, guest onboarding gatekeeper, and route parameters.
UI Component System components/ Atomic design presentation components (CarProgressContainer, BatteryStatusCard, CostSummaryCard) styled with NativeWind v4.
Client State Management stores/ 6 Zustand stores handling auth tokens, multi-session charging, unread counts, profile balances, station filters, and session expired modal.
Server State & Caching queries/, libs/query/ TanStack React Query v5 hooks, automatic background refetching, infinite pagination, and domain query key contracts.
Domain Logic & Hooks hooks/, utils/ Custom hooks encapsulating state machines, Reanimated 3 UI worklets (~15 FPS), NetInfo observers, and 1 EVP = 1,000 VND math.
API & Interceptor Pipeline api/ Dual-instance Axios clients, service routing, JWT Bearer attachment, 401 refresh mutex with rotatedByOtherFlow, and localized error translation.
Secure & Local Persistence storage/ Hardware-backed Expo SecureStore (AES-256) for auth secrets and AsyncStorage for preferences and 24h brute-force lockout hashes.
Global Contexts contexts/ React Context providers for Theme switching (Dark/Light), i18n localization (VI/EN/CH), and Network reachability (NetInfo).

1.4 Definitions, Acronyms & Conventions

Term / Acronym Full Form Design Definition in evp-app
CPMS Charge Point Management System Authoritative cloud backend orchestrating EV chargers, transactions, tariffs, and driver billing.
EVSE Electric Vehicle Supply Equipment The physical charging station pedestal containing one or more power connectors.
EVP EV Power Point Currency Platform digital billing unit strictly pegged to Vietnamese Dong at 1 EVP = 1,000 VND.
SoC State of Charge Vehicle traction battery charge level expressed as an integer percentage (0–100%).
isAcNoSoc AC Charger SoC Suppression Flag Safety invariant hiding battery percentage and rendering a blurred car silhouette when charging via AC posts lacking telemetry.
Mutex Mutual Exclusion Queue Concurrency control pattern in api/interceptors.ts serializing parallel 401 refresh requests into a single network call.
rotatedByOtherFlow Token Concurrency Recovery Flag Interceptor branch recovering from 400/401 refresh errors if another parallel asynchronous flow has already successfully rotated tokens.
RTM Requirements Traceability Matrix Bidirectional mapping linking SRS requirements to concrete code implementation artifacts.

1.5 Standards Compliance & References

  • IEEE Std 1016-2009: IEEE Standard for Information Technology — Systems Design — Software Design Descriptions.
  • IEEE Std 830-1998: IEEE Recommended Practice for Software Requirements Specifications (docs/SRS.html).
  • OCPP 1.6J / 2.0.1: Open Charge Point Protocol JSON specification over WebSockets (CPMS to EVSE).
  • RFC 7519: JSON Web Token (JWT) architecture for stateless, cryptographically signed API authorization.
  • React Native New Architecture: Fabric Native Renderer and TurboModules on React Native 0.81 and Expo SDK 54.

1.6 Requirements Traceability Matrix (RTM)

In accordance with IEEE Std 1016-2009 Section 5, this matrix maps all 24 Functional Requirements (FRs) and 11 Non-Functional Requirements (NFRs) defined in the companion SRS (docs/SRS.html) to concrete code symbols, screens, stores, hooks, and API clients within the evp-app repository:

1.6.1 Functional Requirements Traceability Matrix

SRS Requirement ID Feature Name Primary Screen / Route Zustand Store / React Query API Service Client
FR-AUTH-01..03 Phone OTP, Password Setup & Google Sign-In app/(auth)/*screens/auth/* useAuthStore (stores/auth.store.ts) AuthServices (api/services/auth.ts)
FR-AUTH-04 Driver Profile, Avatar & Debt Tracking app/(tabs)/profile.tsxscreens/profile/ProfileScreen.tsx useProfileStore, useAuthStore UserServices (api/services/user.ts)
FR-STN-01..03 Station Map, Cluster Markers & Filter app/(tabs)/stations.tsxscreens/stations/* useStationQuery, useAllStationQuery, useChargingStationStore StationServices (api/services/station.ts)
FR-CHG-01..05 5-Step Charging Session Machine app/charging/connect.tsxscreens/charging/ChargingSessionScreen.tsx useChargingStore, useChargingSession, useChargingStep ChargingServices (api/services/charging.ts)
FR-CHG-06 AC Battery SoC Suppression (isAcNoSoc) screens/charging/steps/ChargingStep.tsx useChargingStep (derived isAcNoSoc guard) ChargingServices.getChargingSessionDetail()
FR-VEH-01..02 EV Garage & Spending Limits app/vehicle/index.tsxscreens/vehicle/* useVehiclesQuery, useVehicleDetailQuery VehicleServices (api/services/vehicle.ts)
FR-CRD-01..03 RFID Charge Cards & Scratch PIN app/charging-card.tsxscreens/profile/MyVouchersScreen.tsx useChargeCardList (useMyChargeCardsQuery) WalletServices (api/services/wallet.ts)
FR-WAL-01..04 EVP Wallet, VNPay Top-Up & Ledger app/top-up/index.tsxscreens/wallet/* useTopupStatusQuery, useTopupHistoryQuery, useProfileStore WalletServices (api/services/wallet.ts)
FR-INV-01..03 VAT Tax Profiles & PDF Invoices app/invoice/index.tsxscreens/invoice/* useInvoiceListQuery, useUserInvoiceProfileQuery InvoiceServicesInvoiceProfileServices
FR-NOT-01..02 Push Notifications & REST Feed app/notifications/index.tsxscreens/notifications/* useNotificationStore (stores/notification.store.ts) NotificationServices (api/services/notification.ts)

1.6.2 Non-Functional Requirements Traceability Matrix

SRS NFR ID Quality Attribute & Target Architectural Mechanism Governing Code Artifacts SDD Section
NFR-PRF-01 60 FPS Animation & Zero Dropped Frames Reanimated 3 UI Worklet throttling (65ms / ~15 FPS); CarProgress SVG clipping hooks/useAnimatedNumber.ts, CarProgressContainer.tsx Section 3.5
NFR-PRF-02 Telemetry Polling Interval ≤10,000 ms Dynamic interval polling hook with exponential backoff on errors hooks/charging/useChargingStep.ts, useChargingSession.ts Section 6.1
NFR-PRF-03 Startup Splash ≥2,500 ms Zero Layout Shift SplashScreen.preventAutoHideAsync() with Space Grotesk pre-loading app/_layout.tsx (MIN_SPLASH_DURATION_MS) Section 3.2
NFR-SAF-01 Hardware Relay Interlock on Start Explicit driver CTA requirement; connector must reach IN_USE + ≥1,800ms stabilization ConnectingStep.tsx, useChargingStep.ts Section 6.1
NFR-SAF-02 Emergency Disconnect & Stop Slider Swipe-to-stop gesture initiating two-phase settlement with <StoppingChargeOverlay /> SwipeStopSlider.tsx, StoppingChargeOverlay.tsx Section 6.1
NFR-SEC-01 Hardware-Backed Token Encryption Expo SecureStore utilizing iOS Keychain and Android Keystore with AES-256-GCM storage/secure-storage.ts, storage/token-storage.ts Section 7.1 / 7.2
NFR-SEC-02 Bearer Token & PII Redaction in Logs maskToken and sanitizeAuthPayload utility funnels in error handlers api/errors.ts (getApiErrorLog) Section 5.5
NFR-SEC-03 24-Hour Anti-Brute-Force Lockout 64-bit FNV/Murmur phone number hashing with daily calendar reset guard storage/app-storage.ts (change_password_lock_${hash}) Section 7.2
NFR-SEC-04 Immediate Session Teardown on Revocation Synchronous purging of all 6 Zustand store slices and queryClient.clear() stores/auth.store.ts (clearSessionState()) Section 4.4 / Fig 4.1
NFR-QLT-01 Zero Broken Tables on A4 Print / PDF Embedded @media print stylesheet with orphan/widow suppression and page breaks docs/SDD.html, docs/SRS.html Section 1.4
NFR-QLT-02 Multi-Language Localization (VI/EN/CH) Synchronous i18n dictionary lookup with fallback and EXCLUDED_CODES 401 bypass contexts/I18nContext.tsx, api/errors.ts Section 5.5
NFR-QLT-03 Offline & Weak Network Detection NetInfo cellular/wifi observer driving full-screen centered blocking/non-blocking modal contexts/NetworkContext.tsx, NetworkStatusOverlay.tsx Section 5.4 / 8.3

2. System Architecture & Design Principles

2.1 Clean Layered Architecture Overview

The EV Power Mobile Application enforces a strict 4-layer Clean Layered Architecture. Dependencies flow strictly downward from Presentation to Infrastructure; higher layers consume domain facades without knowing underlying network or persistence details:

LAYER 1: PRESENTATION & ROUTING LAYER (Expo Router 54 & React Native 0.81) File-Based Routes (app/) _layout, (tabs), charging/connect, auth Screen Controllers (screens/) ChargingSession, Setup, MyVouchers, Guests Atomic UI Components (components/) CarProgressContainer, BatteryStatusCard LAYER 2: DOMAIN HOOKS & INTERACTIVE FACADE (Stateful Orchestration) useChargingSession & useChargingStep State Machine, Live Polling, AC SoC Guard & Reanimated Worklets Context Facades & Hardware Observers NetworkContext, ThemeContext, I18nContext, useAppActive LAYER 3: CLIENT STATE (Zustand) & SERVER CACHE (TanStack React Query) Client Stores (stores/) auth, charging, notification, profile, session-expired, charging-station Query Cache & Keys (queries/) queryClient: 30s stale, 5m gc, stations, orders, wallet, vehicles API TRANSPORT & MUTEX (api/) Axios Dual-Instance, 401 Mutex, rotatedByOtherFlow, Masking PERSISTENCE STORAGE (storage/) SecureStore (AES-256 Tokens), AsyncStorage (Preferences & Lockout)
Fig 2.1: Clean Layered Architecture & Data Flow of the EV Power Mobile Application
Layer Module Responsibilities Allowed Downward Dependencies
1. Presentation Layer Expo Router 54 file-based routing, screen controllers, atomic UI components, gestures, and NativeWind styling. Layer 2 (Hooks & Facades), Layer 3 (Zustand Stores & React Query hooks)
2. Domain Hooks & Facades Custom hooks encapsulating state machines, Reanimated 3 UI worklets, app lifecycle observers, and context facades. Layer 3 (Zustand & Query Cache), Layer 4 (API Clients & Storage)
3. Client State & Server Cache Zustand client stores (ephemeral state) and TanStack React Query v5 cache (remote server state & invalidation). Layer 4 (API Transport & Storage Engines)
4. API Transport & Storage Dual Axios instances, 401 refresh token mutex queue, SecureStore AES-256 encryption, and AsyncStorage preferences. Native Mobile OS Platforms (iOS Keychain, Android Keystore, Network Stack)

2.2 Core Architectural Design Patterns

  • Dual-Instance Axios Pattern: Separates public unauthenticated calls (e.g. login, token refresh) from protected Bearer-authenticated calls to prevent circular dependency loops during 401 token refresh.
  • Mutex Refresh Queue Pattern with Concurrency Recovery: Suspends concurrent failing requests during token rotation, resolving them upon token exchange, with automatic fallback recovery via rotatedByOtherFlow if another asynchronous flow already rotated tokens.
  • Multi-Session Keying Pattern: Indexes active charging sessions by physical coordinates (chargeBoxCode_connectorNo) in Zustand memory, ensuring concurrent or multi-vehicle fleet charging sessions never overwrite each other.
  • SoC Suppression Guard Pattern (isAcNoSoc): Safety pattern hiding battery percentage and rendering a blurred car silhouette when charging at AC posts lacking battery telemetry, preventing driver panic from false 0% readouts.
  • UI Thread Reanimated Worklets: Bypasses the JavaScript bridge by executing high-frequency telemetry animations on the native UI thread via Reanimated 3 SharedValues, throttled to 65ms (~15 FPS).

3. Component & UI Architecture

3.1 Expo Router File-Based Routing Topology

The application leverages Expo Router v4 (Expo SDK 54), providing type-safe, URL-driven routing modeled after file-system conventions. The routing tree is structured into clear navigation hierarchies:

  • Root & Onboarding Gatekeeper (app/index.tsx): Determines whether the device has completed onboarding, evaluates authentication status via useAuthStore, and routes either to /(tabs)/home (authenticated), /(auth)/login, or guest screens.
  • Authentication Stack (app/(auth)/): Public authentication stack containing login.tsx, register.tsx, forgot-password.tsx, and reset-password.tsx. Note that OTP verification is handled inline as an active step within ForgotPasswordScreen.tsx and RegisterScreen.tsx rather than as an isolated standalone route.
  • Authenticated Driver Tabs (app/(tabs)/): Authenticated main driver experience featuring a custom persistent floating bottom bar:
    • (tabs)/home.tsx → Driver dashboard, active charging banner, quick actions.
    • (tabs)/orders/ → Nested directory tree with _layout.tsx controller, historical charging sessions, and receipt sub-routes.
    • (tabs)/qr-scan.tsx → Raised camera QR scanner with physical connector manual entry modal.
    • (tabs)/stations.tsx → Interactive Leaflet/Map view with cluster markers, search drawer, and connector filters.
    • (tabs)/profile/ → Nested directory tree with _layout.tsx controller, managing wallet balance, RFID charge cards, vehicles, invoices, and help center.
  • Active Charging State Machine Route (app/charging/connect.tsx): Mounts screens/charging/ChargingSessionScreen.tsx, which hosts the core 5-step charging machine (PlugInStep, ConnectingStep, ChargingStep, SuccessScreen, FailedScreen). Note that app/charging-detail.tsx re-exports OrderDetailScreen (order receipt), not the active state machine.
  • Guest Exploration Subsystem (screens/guests/): Provides unauthenticated discovery across 6 dedicated screens: GuestHomeScreen, GuestStationScreen, GuestSearchScreen, GuestStationDetailScreen, GuestScanGateScreen, and GuestProfileScreen.

3.2 Root Provider Tree Lifecycle (app/_layout.tsx)

The root layout component (app/_layout.tsx) manages the startup bootstrap lifecycle, font asset loading, native splash dismissal, and strict context provider nesting:

GestureHandlerRootView QueryProvider (queryClient) I18nProvider (vi, en, ch) ThemeProvider (Light / Dark) NetworkProvider (NetInfo) <Stack /> (Expo Router) <NetworkStatusOverlay /> <TikTokToast /> (Notifications) <SessionExpiredModal />
Fig 3.1: Complete Root Provider Hierarchy & Global UI Overlays Tree (app/_layout.tsx)
Lifecycle Timing Invariant: Native splash screen auto-hiding is prevented via SplashScreen.preventAutoHideAsync(). A minimum splash duration of 2,500 ms (MIN_SPLASH_DURATION_MS) is enforced in combination with useFonts pre-loading (Space Grotesk 5 weights) to ensure zero visual layout shifts or font flashes before the main UI renders.

3.3 Custom Floating Curved SVG Tab Bar Architecture

The bottom navigation bar (app/(tabs)/_layout.tsx) features a proprietary floating SVG notch architecture with a center-cutout dome accommodating the raised QR Scanner button:

  • SVG Path Math: Uses exact Quadratic Bézier (Q) and Elliptical Arc (A33,33) curves to sculpt the center dome notch dipping downwards to wrap around the 60px circular QR scan button. Zero Cubic Bézier (C) commands are utilized; NOTCH_DEPTH = 22 is declared in constants but unused in the geometric path math.
  • Top Rim Active Indicator: A 4px absolute bar (Animated.View style={{ height: 4, position: 'absolute', top: 0 }}) glides along the top edge of the tab bar using Animated.spring(indicatorPosition, { tension: 65, friction: 10, useNativeDriver: true }).
  • QR Tab Transparency: When the driver taps the center QR button (tab index 2), the indicator's opacity smoothly fades to 0 via Animated.timing(indicatorOpacity, { toValue: 0, duration: 200 }), preventing visual artifacts beneath the elevated camera button.

3.4 Design System, Tokens & Styling Architecture

The design system is constructed on NativeWind v4 (Tailwind CSS preset). Styling tokens are dynamically harmonized across Dark and Light modes through ThemeContext.tsx:

Design Token Light Theme (#F8FAFC bg) Dark Theme (#0B0F15 bg) Usage & Semantic Role
colors.primary #088178 (Deep Teal) #14B8A6 (Bright Cyan-Teal) Primary brand actions, active state highlights, submit buttons.
colors.card #FFFFFF (Pure White) #161D26 (Rich Obsidian) Card backgrounds, modal surfaces, floating tab bar body.
colors.cardBorder #E2E8F0 (Slate 200) rgba(255, 255, 255, 0.08) Subtle borders on cards, input fields, and tab bar edges.
colors.text #0F172A (Slate 900) #F1F5F9 (Slate 100) Primary headings, key data readouts, active labels.
colors.textMuted #94A3B8 (Slate 400) #64748B (Slate 500) Timestamps, secondary telemetry labels, inactive tab icons.
colors.warning #C9992E (Amber Gold) #FACC15 (Amber 400) AC Charger SoC unsupported alert, low wallet balance warning.

3.5 Micro-Interactions & High-Performance Animations

To maintain a fluid 60 FPS experience on mobile devices during intensive live telemetry polling:

  • CarProgressContainer Liquid Wave Silhouette: Rather than a generic circular progress ring, CarProgressContainer.tsx renders an SVG side-profile car silhouette with an animated liquid wave clipping fill (buildWavePath). Reanimated shared values drive sinusoidal horizontal translation and vertical fill height matching batteryPct.
  • Throttled Reanimated UI Worklet: The useAnimatedNumber hook runs a Reanimated 3 UI worklet throttled to 65ms (~15 FPS) via useAnimatedReaction, interpolating numeric metrics (kwh, amount in EVP, batteryPct) over 1,200 ms with cubic easing, completely offloading the JS thread.
  • Background Battery Conservation: The useAppActive() hook detects when the mobile OS transitions the app to the background (or another tab). When inactive, UI animations are immediately paused to conserve CPU cycles and battery.

4. State Management & Data Fetching Architecture

4.1 Client-Side State: Zustand Store Architecture

The application uses Zustand 4.5 for client-side state management. State is divided across 6 dedicated, loosely-coupled domain stores:

Zustand Store Source File Primary State & Responsibilities Persistence Mechanism
useAuthStore stores/auth.store.ts accessToken, user profile, loading, initialized, auth bootstrap, login/register mutations. SecureStore (Tokens) + Memory
useChargingStore stores/charging-store.ts sessions: Record<string, ChargingSession>, active session keys, duration calculation, server session rehydration. In-Memory (Synchronized with CPMS)
useNotificationStore stores/notification.store.ts unreadCount: number, notifications: NotificationItem[], read state toggle, resetNotifications() teardown. In-Memory (Refetched via REST)
useProfileStore stores/profile.store.ts Driver avatar URL, wallet balance in EVP, outstandingPoint debt tracking, vehicle selection. In-Memory
useSessionExpiredStore stores/session-expired.store.ts isOpen: boolean, controls the global Session Expired modal dialog. In-Memory (Single-fire trigger)
useChargingStationStore stores/charging-station-stores.ts Selected station, connector filter criteria (power, standard), search keyword, station detail drawer state. In-Memory

4.2 Multi-Session Keying & State Isolation

A critical architectural pattern in stores/charging-store.ts is the Multi-Session Keying model. Instead of storing a single global active session, sessions are stored in an indexed dictionary keyed by physical hardware coordinates:

/** Unique composite session key for physical charger connector */
export function makeSessionKey(chargeBoxCode: string, connectorNo: number): string {
  return `${chargeBoxCode}_${connectorNo}`;
}

export interface ChargingSession {
  chargeBoxCode: string;
  connectorNo: number;
  stationName: string;
  connectorLabel: string;
  startedAt: number;        // Epoch millisecond timestamp
  elapsedSeconds: number;
  kwh: number;
  batteryPct: number;
  transactionId: number | null;
  hasNavigatedOnStop: boolean;
  hasCompleted: boolean;
}

Architectural Invariant: The canonical delimiter is underscore (${chargeBoxCode}_${connectorNo}). For backward compatibility with legacy socket payloads, stores/charging-store.ts:140 also safely falls back to colon splitting if encountered. This dictionary indexing guarantees that concurrent charging sessions (e.g. multi-vehicle fleet accounts) never overwrite each other's live metrics.

4.3 Server-Side Cache: TanStack React Query Configuration

Remote server state is managed through TanStack React Query v5 (libs/query/client.ts). The global client is configured with strict staleness and garbage collection policies tailored for mobile networks:

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 1,                          // Fail fast to allow light retry interceptor to handle 5xx
      staleTime: 30000,                  // 30 seconds: prevents redundant network requests
      gcTime: 300000,                    // 5 minutes: cache retention in memory
      refetchOnReconnect: "always",       // Immediate refetch when network comes online
      refetchOnMount: true,
      refetchOnWindowFocus: false,       // Suppressed on React Native mobile clients
    },
  },
});

4.4 Unified Session Teardown Protocol

When authentication tokens expire or the driver explicitly logs out, a hard session invalidation occurs. To prevent stale data leakage across driver sessions, stores/auth.store.ts executes a synchronous teardown protocol:

LOGOUT / 401 HARD Session Invalidation clearSessionState() authSessionVersion += 1 processQueue(Error, null) tokenStorage.clearTokens() useAuthStore.getState().clear() useNotificationStore.getState().resetNotifications() useProfileStore.getState().resetProfile() useChargingStore.getState().clearAllSessions() queryClient.clear() (Purges all server cache)
Fig 4.1: Unified State Teardown & Cache Purge Protocol upon Session Invalidation

Calling clearSessionState() atomically executes:

  • useAuthStore.getState().clear() → Purges user entity and auth status.
  • useNotificationStore.getState().resetNotifications() → Clears unread counts and cached alerts.
  • useProfileStore.getState().resetProfile() → Resets balance and personal profiles.
  • useChargingStore.getState().clearAllSessions() → Wipes all active charging timers and telemetry.
  • queryClient.clear() → Purges 100% of cached query responses from memory.

5. Networking, API Client & 401 Mutex Architecture

5.1 Dual-Instance Axios Pattern

The networking layer (api/client.ts) instantiates two distinct Axios instances:

  1. Public Client (publicClient): Used exclusively for unauthenticated endpoints (login, register, forgot password, and token refresh). It has no Bearer token interceptor, eliminating circular dependency deadlocks.
  2. Protected Client (protectedClient): Injects Authorization: Bearer <token> headers, monitors responses for 401 UNAUTHORIZED status, and interfaces with the token refresh mutex queue.

5.2 Dynamic Microservice Path Construction

Backend services are structured as microservices under unified API gateway prefixes. api/client.ts dynamically builds service base URLs according to the target domain module:

Service Name (ServiceName) Base Path Constructed Target Domain Operations
"auth" /api/v1/auth/* Driver login, token refresh, OTP verification, password reset.
"charging" /api/v1/charging/* Start/stop session, live telemetry polling, charging orders, transactions.
"stations" /api/v1/stations/* Charging station discovery, geographic search, connector availability.
"wallet" /api/v1/wallet/* Top-up creation, VNPay verification, RFID card activation (/cards), ledger history.
"invoices" /api/v1/invoices/* Invoice history listing, binary PDF streaming (/:id/download).
"invoice-profiles" /api/v1/invoice-profiles/* Personal and corporate VAT tax invoice profiles.
"vehicles" /api/v1/vehicles/* Electric vehicle garage, model specifications, fleet spending limits (PATCH /:id).
"notifications" /api/v1/notifications/* Expo push token registration (/devices), notification inbox.
"user" /api/v1/user/* Driver profile details, avatar upload, account metadata.

5.3 Thread-Safe 401 Refresh Mutex Queue Sequence

When access tokens expire, concurrent protected requests risk triggering a stampede of refresh calls. api/interceptors.ts enforces a thread-safe Mutex Queue:

Caller A (GET /charging/orders) Caller B (GET /user/profile) Axios Interceptor (Mutex) Auth Backend API 1. Request fails with 401 UNAUTHORIZED LOCK isRefreshing = true 2. Request B fails with 401 Push to failedQueue[] 3. POST /api/v1/auth/refresh (refreshToken) 4. 200 OK: newAccessToken & newRefreshToken Save new tokens to SecureStore & AuthStore (or recover via rotatedByOtherFlow) 5. Retry Request A with new Bearer Token → 200 OK 6. Drain failedQueue: Retry Request B → 200 OK FREE isRefreshing = false
Fig 5.1: Thread-Safe 401 Refresh Token Mutex Queue & Request Replay Sequence

The mutex implements 4 critical engineering invariants:

  1. Single-Flight Refresh: While isRefreshing = true, subsequent failing 401 calls push their promise resolve/reject handlers into failedQueue[].
  2. Stale Response Guard (_authSessionVersion): If a logout occurs while a refresh call is inflight, the response version is compared against authSessionVersion. Stale refresh results are safely discarded.
  3. Single-Fire Session Expired Notification: If token refresh fails with 401/403, hasNotifiedSessionExpired ensures only one modal dialog is presented, preventing alert stacking.
  4. Concurrent Rotation Recovery (rotatedByOtherFlow): If the refresh attempt fails with 400/401/403, the interceptor checks SecureStore (api/interceptors.ts:275-285). If another concurrent flow already rotated the refresh token (refreshTokenUsed !== latestRefreshToken), the queue is resolved with latestAccessToken and retried without dropping the session.

5.4 Network Resiliency & Weak Network Detection

The networking stack integrates with @react-native-community/netinfo via NetworkContext.tsx:

  • Connection State: Distinguishes between completely offline (isConnected === false) and weak/unstable connectivity (isWeakConnection === true, e.g. 2G or poor cellular handoff).
  • Idempotent Safe Retries: Read-only HTTP GET requests are automatically retried once on transient socket timeouts (ECONNABORTED). State-mutating POST/PATCH operations are never retried automatically to prevent duplicate transactions.

5.5 Localized Error Translation Pipeline & PII Redaction

Backend errors return standardized error codes (e.g. INVALID_CREDENTIALS, WALLET_INSUFFICIENT_BALANCE). The interceptor transforms these into user-facing localized messages using api/errors.ts:

  • EXCLUDED_CODES Proxy Guard: The code "UNAUTHORIZED" is explicitly excluded from the i18n error code translation proxy. This ensures that 401 errors fall through directly to the token refresh mutex queue rather than prematurely rejecting with a localized error string.
  • Sensitive Credential & Token Redaction: To satisfy NFR-SEC-02, maskToken and sanitizeAuthPayload redact bearer tokens, refresh tokens, passwords, and PINs from debug console logs (e.g., displaying eyJhbGci...4f8a).

6. Detailed Subsystems Coding Design (LLD)

6.1 Subsystem 1: 5-Step EV Charging Session State Machine

The core business value of EV Power resides in screens/charging/ChargingSessionScreen.tsx (mounted at app/charging/connect.tsx). The charging lifecycle is governed by a 5-step finite state machine:

1. PLUG STEP PlugInStep.tsx Polls status (5,000 ms) Awaits PREPARING DRIVER TAP handleStartCharge 2. CONNECTING STEP ConnectingStep.tsx Awaits IN_USE status + Min Delay ≥1,800ms Tx Active 3. ACTIVE CHARGING (ChargingStep.tsx) 10-Second Telemetry Polling: GET /sessions/{id} • CarProgress wave clip DC Fast / Valid SoC socPercent > 0 Car Silhouette Fill + % Badge AC Charger Fallback isAcNoSoc === true Suppress % Badge + Blur Car Active Instrumentation & Swipe-to-Stop Duration (clockTimer) • Energy (kWh) • Power (kW) • EVP/VND Cost useAnimatedNumber worklet (~15 FPS) • SwipeStopSlider gesture Driver swipes → sets step: "terminating" → POST /stop 4. TERMINATION & SETTLEMENT <StoppingChargeOverlay /> Awaits CHARGING_STOPPED push Polls GET /orders/{id} @ 1000ms Resolves to COMPLETED / CANCELED 5A. SUCCESS (Normal) SuccessScreen.tsx orderId, totalEnergyWh, points Invoice profile & PDF receipt 5B. FAILED / CANCELED FailedScreen.tsx CABLE_DISCONNECTED / ABORT Cable unlock instruction
Fig 6.1: 5-Step EV Charging State Machine, Telemetry Polling, AC Fallback, and Two-Phase Settlement

The state machine transitions follow strict hardware and safety guards:

State Machine Step Component / Hook Transition Preconditions & Guard Rules Hardware / API Action
1. plug PlugInStep.tsx Station connector status reaches "PREPARING" (cable inserted). Driver MUST TAP "Start Charging" CTA. Dispatches POST /api/v1/charging/sessions/start, initialising transactionId.
2. connecting ConnectingStep.tsx Connector status reaches "IN_USE" AND elapsed time ≥ connectingMinDurationMs (1,800 ms). Hardware contactor relays energize; transitions to Step 3.
3. charging ChargingStep.tsx Active 10-second polling (GET /sessions/{id}). Derives isAcNoSoc guard: if true, hides % badge and blurs car silhouette. Telemetry drives Reanimated wave fill (CarProgressContainer). Driver can swipe SwipeStopSlider.
4. terminating StoppingChargeOverlay.tsx Driver swipes to stop or server terminates. Displays stopping overlay; awaits CHARGING_STOPPED push alert containing orderId. Polls GET /api/v1/charging/orders/{orderId} every 1,000 ms until status is COMPLETED or CANCELED.
5. completed / error SuccessScreen / FailedScreen Order status resolves to COMPLETED (success) or CANCELED/fault (error). Presents itemized receipt with points billed and VAT invoice option, or cable unlock instructions.

6.2 Subsystem 2: RFID Charge Card Management (charge-card)

Physical RFID charging card management is mounted at app/charging-card.tsx rendering screens/profile/MyVouchersScreen.tsx:

  • Status Filter Tabs: Tabbed segmented control (ChargeCardStateTabs.tsx) filters cards by status: ALL, ACTIVE (valid point lots), EXPIRING_SOON (≤7 days remaining), USED_UP (zero balance), and EXPIRED.
  • Scratch Card PIN Activation: Modal dialog accepts the 16-character serial and scratch PIN, dispatching POST /api/v1/wallet/cards/activate via WalletServices.redeemChargeCard(), immediately invalidating ['my-charge-cards'] and ['profile'] caches.
  • Point Lot Representation: Each card represents a promotional point voucher lot with face value (facePoint), remaining points (pointRemaining), and expiry timestamp (pointExpireAt).

6.3 Subsystem 3: EVP Digital Wallet & VNPay Integration

Platform transactions strictly observe the currency conversion rate 1 EVP = 1,000 VND:

  • Top-Up Creation: Driver selects EVP point package; app dispatches POST /api/v1/wallet/topups receiving VNPay checkout URL (paymentUrl) and topupId.
  • Embedded WebView Checkout: Payment is processed inside an in-app <TopupPaymentWebView /> modal, avoiding external browser switches and preserving mobile context.
  • Reactive Push Settlement: Upon VNPay completion, backend issues a TOPUP_SUCCESS push notification. The app invalidates ['topup-status', id], refetches GET /api/v1/wallet/topups/{id}, and refreshes useProfileStore balance.

6.4 Subsystem 4: Electronic VAT Invoices & PDF Streaming

Corporate and individual tax invoice management is implemented under app/invoice/:

  • Invoice Profiles: Managed via InvoiceProfileServices (POST /api/v1/invoice-profiles) supporting Tax Code (Mã số thuế), Company Legal Name, and registered Tax Address.
  • Binary PDF Streaming: Invoice PDF downloads invoke InvoiceServices.downloadInvoice({ id }), dispatching GET /api/v1/invoices/{id}/download with responseType: "arraybuffer" and header Accept: application/pdf. The raw buffer is cached via Expo FileSystem and presented via Expo Sharing.

6.5 Subsystem 5: Push Notifications & Event Reconciliation

Real-time updates are mediated via Expo Push Notifications (api/services/notification.ts):

  • Device Registration: NotificationServices.registerForPushNotifications() obtains the push token and registers device metadata at POST /api/v1/notifications/devices.
  • Foreground State Reconciliation: In app/_layout.tsx, when an ORDER_CREATED notification arrives, the app invokes ChargingServices.getChargingSessionActive() and updates chargingStore.restoreSessions(), reactively displaying active sessions in <ActiveSessionList /> on HomeScreen.
  • Background Notification Route Gap: NotificationServices.addNotificationResponseReceivedListener is provided for tapping background notifications; mounting it in root layout is scheduled for future deep-linking routing.

6.6 Subsystem 6: Vehicle Fleet Garage & Spending Limits (vehicles)

Vehicle management is implemented under app/vehicle/ and screens/vehicle/:

  • Garage Listing & Details: useVehiclesQuery fetches user EVs from GET /api/v1/vehicles.
  • Per-Session Spending Cap: Corporate fleet accounts can set session spending limits via VehicleServices.updateVehicle(id, { label, maxPointPerSession }) dispatching PATCH /api/v1/vehicles/{id}, preventing runaway vehicle charging debt.

7. Storage & Security Architecture

7.1 Multi-Tier Storage Architecture

Data persistence in the EV Power mobile app is segregated into three distinct isolation tiers based on data sensitivity and lifecycle characteristics:

  1. Tier 1: Hardware-Backed Encrypted Storage (Expo SecureStore): Utilizes hardware keystores (iOS Keychain with kSecAttrAccessibleAfterFirstUnlock and Android Keystore with AES-256-GCM encryption). Only authentication and session-critical secrets are persisted here.
  2. Tier 2: Non-Sensitive Persistent Storage (AsyncStorage / app-storage.ts): Lightweight, unencrypted key-value storage used for UI theme modes, language selections, dismissible tutorial flags, and brute-force lockout hashes.
  3. Tier 3: In-Memory Volatile State (Zustand / React Query Cache): All active charging telemetry, real-time prices, and temporary user inputs reside strictly in RAM and are automatically purged upon application termination or session logout.

7.2 Storage Boundary Catalog

In accordance with IEEE Std 1016 Data Persistence Viewpoint, the complete catalog of persistent storage keys in the evp-app client is detailed below:

Storage Key Storage Engine Encryption Standard Payload / Purpose Clear / Invalidation Trigger
STORAGE_KEYS.ACCESS_TOKEN SecureStore AES-256 (Hardware Keystore) Short-lived JWT Bearer token for CPMS REST calls Logout / 401 Hard / Session Expiry
STORAGE_KEYS.REFRESH_TOKEN SecureStore AES-256 (Hardware Keystore) Long-lived refresh token for token exchange Logout / Explicit Account Reset
STORAGE_KEYS.RESET_TOKEN SecureStore AES-256 (Hardware Keystore) Ephemeral token for Forgot Password confirmation Password change completed (clearResetToken())
change_password_lock_${hash} AsyncStorage 64-bit FNV/Murmur Hash JSON: { failedCount, lockedUntil, lastAttemptAt } 24h expiry / calendar day change / successful reset
app_theme_mode AsyncStorage Unencrypted "system" | "light" | "dark" user preference Never (Preserved across logins)
app_language AsyncStorage Unencrypted "vi" | "en" | "ch" selected locale code Never (Preserved across logins)
has_seen_location_onboarding AsyncStorage Unencrypted Boolean string ("true") suppressing location permission prompt Never (Device-level flag)
has_seen_language_onboarding AsyncStorage Unencrypted Boolean string ("true") suppressing language picker modal Never (Device-level flag)
partner_hero_bg_color AsyncStorage Unencrypted Hex color string for B2B co-branding hero background Replaced on partner reload

8. Error Handling, Resilience & Telemetry Fallback

8.1 Multi-Tier Exception Handling Framework

Errors propagate through a 4-tier funnel designed to prevent unhandled promise rejections and network failures:

Tier 1: Network
Axios interceptors trap network timeouts (ECONNABORTED), 401s, and 5xx server errors, automatically attempting light retries for GET requests.
Tier 2: Localization
Backend error codes (INVALID_OTP, INSUFFICIENT_BALANCE) are translated into localized UI strings via applyErrorCodeMessage before rejecting.
Tier 3: React Query
React Query catches rejected promises, controls retry limits (retry: 1), and sets error state without throwing fatal JS exceptions.
Tier 4: UI Toast / Banner
Components observe error states and present animated toasts (TikTokToast) or inline retry banners (SetupErrorBanner.tsx).
Architectural Reliability Observation: The root provider tree in app/_layout.tsx currently lacks a top-level React ErrorBoundary component. Introducing an AppErrorBoundary wrapper around <Stack /> is strongly recommended to protect against synchronous rendering crashes (e.g. SVG path parse errors or Reanimated worklet runtime faults) and provide a graceful user recovery UI.

8.2 Live Telemetry Offline Degradation & Mathematical Utility

During active charging sessions, the mobile client must remain resilient when traversing areas with poor or intermittent cellular connectivity (e.g. underground parking basements):

  • Actual Runtime Degradation: If telemetry polling fails due to connection loss, useChargingStep.ts continues ticking the client-side elapsed timer clock (setInterval(syncElapsedSeconds, 1000)). The instrumentation metrics (energyWh, socPercent, estimatedPoint) remain frozen at their last authoritative server values until connectivity is restored and polling resumes.
  • Dead-Reckoning Mathematical Model: stores/charging-store.ts defines a mathematical estimation helper (getChargingProgress) based on nominal charger output (CHARGER_POWER_KW = 50) and battery capacity (BATTERY_CAPACITY_KWH = 75). This module currently serves as a standalone calculation utility and is slated for active UI integration in a future release with an explicit driver notification badge.

8.3 Global Overlays & Modal Recovery Workflows

The application mounts two global recovery overlays in app/_layout.tsx:

  • NetworkStatusOverlay.tsx: Renders as a full-screen centered modal dialog with an opaque backdrop (bg-black/40). When !isConnected, it activates a blocking modal dialog with a manual "Retry Now" trigger. When isWeakConnection === true, it displays an amber status notice with non-blocking touch interaction (pointerEvents="box-none").
  • SessionExpiredModal.tsx: Controlled by session-expired.store.ts. When refresh tokens expire or are rejected by the CPMS, the session teardown protocol purges all in-memory state and cache, and this modal prompts the driver to confirm re-login, executing router.replace("/(auth)/login").

9. Testing, Verification & Coding Standards

9.1 TypeScript Typing & Architectural Boundaries

  • Strict Typing: Zero usage of unconstrained any in domain modules. Generic typing is enforced on all API responses (this.api<T>()).
  • Layered Type Separation: Domain entities reside under types/<domain>/*.types.ts (e.g. types/charge-card/, types/vehicle/), while remote REST service DTO payloads reside under types/services/*.types.ts.
  • Immutability: State updates in Zustand stores utilize spread patterns or functional updaters, ensuring strict referential equality checks for React render optimizations.

9.2 Unit & Integration Testing Strategy

  • Zustand Store Unit Tests: Verify store actions, multi-session key mapping, duration calculations, and atomic session teardown in isolation with mocked storage engines.
  • Interceptor Mutex Tests: Test parallel 401 token refresh queue concurrency, ensuring only 1 refresh call is dispatched, failed requests are replayed with new tokens, and rotatedByOtherFlow recovers without session loss.
  • State Machine Guard Tests: Validate that useChargingStep strictly enforces the explicit CTA tap in Step 1, the 1,800 ms hardware delay in Step 2, and the isAcNoSoc suppression guard on AC charging sessions.

9.3 Mocking & Simulation Infrastructure

  • Axios Mock Adapter: Simulates REST endpoints for testing offline queuing and token refresh without live backend dependencies.
  • NetInfo Mocking: Tests NetworkStatusOverlay behaviors across offline, cellular 2G (weak), and high-speed Wi-Fi states.