Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Roadly Mobile

Roadly Mobile is a Flutter monorepo containing separate customer and driver applications backed by shared, tested Roadly packages.

Products

  • roadly_customer: vehicle rentals and customer ride bookings.
  • roadly_driver: dispatch, live trips, earnings, documents and safety.
  • roadly_catalog: internal visual contract for the Roadly design system.
  • The existing Angular application remains the Roadly administration portal.

Requirements

  • Flutter 3.47 or newer
  • Dart 3.13 or newer
  • Android Studio and an Android SDK for Android development
  • Xcode on macOS for iOS development

Workspace commands

flutter pub get
dart run melos run format
dart run melos run analyze
dart run melos run test

Run an application from its directory:

cd apps/roadly_catalog
flutter run -d chrome

For final visual QA, run the component catalog from the workspace root:

flutter run -t apps/roadly_catalog/lib/main.dart

Its Visual QA controls switch theme, EN/MS/ES/PT locale, 100%–200% text scale and phone/tablet constraints without rebuilding. Follow the complete docs/release-gate.md checklist before producing a store candidate.

Local configuration

Roadly accepts non-secret build configuration through Dart defines:

Define Android emulator value Purpose
ROADLY_API_URL http://10.0.2.2:5000 Connects the emulator to the backend running on the host computer
GOOGLE_WEB_CLIENT_ID Web OAuth client ID Requests a Google ID token intended for the Roadly backend

Use http://localhost:5000 for an iOS simulator. Use the development computer's LAN IP, such as http://192.168.1.20:5000, for a physical device. The computer and physical device must be on the same network, and the firewall must allow the backend port.

Client IDs are public identifiers and may be supplied through Dart defines. Client secrets, provider signing keys, and backend credentials must never be placed in Flutter source code, mobile configuration, or Git.

Google sign-in setup

Roadly Customer uses the official Flutter google_sign_in package to obtain a Google ID token. The token is sent to the Roadly backend, where Better Auth verifies its signature, issuer, audience, and expiry before creating the Roadly session. The driver app intentionally remains email-only.

All OAuth clients below must belong to the same Google Cloud project.

1. Configure Google Auth Platform

  1. Open Google Cloud Console and select or create the Roadly project.
  2. Open Google Auth Platform > Branding.
  3. Configure the app name, support email, and developer contact email.
  4. Open Audience, select External, and add developer Google accounts as test users while the app is in testing mode.
  5. Open Clients to create the OAuth clients described below.

2. Create the Web OAuth client

Create a client with these values:

Application type: Web application
Name: Roadly Web and Backend
Authorized JavaScript origin: http://localhost:4200
Authorized redirect URI: http://localhost:5000/api/v1/auth/session/callback/google

Copy the resulting Web client ID and client secret into the backend .env:

GOOGLE_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-web-client-secret

The Web client ID is also passed to the Flutter customer app as GOOGLE_WEB_CLIENT_ID. The client secret remains backend-only.

3. Obtain the Android signing fingerprints

From PowerShell or the Android Studio terminal:

cd .\apps\roadly_customer\android
.\gradlew.bat signingReport

Find the debug variant and copy its SHA1 value. The equivalent direct keytool command is:

keytool -list -v `
  -keystore "$env:USERPROFILE\.android\debug.keystore" `
  -alias androiddebugkey `
  -storepass android `
  -keypass android

Release and Google Play builds use different signing certificates. Create the corresponding OAuth credential with the release or Play App Signing SHA-1 before distributing the app.

4. Create the Android OAuth client

Create another Google Auth Platform client:

Application type: Android
Name: Roadly Customer Android Debug
Package name: com.roadly.roadly_customer
SHA-1 certificate fingerprint: value from signingReport

Copy its client ID to the backend .env:

GOOGLE_ANDROID_CLIENT_ID=your-android-client-id.apps.googleusercontent.com

The Android package name and SHA-1 must match the build being launched. An incorrect package, fingerprint, or Web server client ID can appear as a configuration error or, on some Android versions, as a canceled sign-in.

5. Optional future iOS OAuth client

The iOS client is not required for Android development. When iOS work begins, create a client with:

Application type: iOS
Name: Roadly Customer iOS
Bundle ID: com.roadly.roadlyCustomer

Then add the result to the backend .env:

GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com

The iOS Runner must also receive the Google iOS client and reversed URL-scheme configuration before Google sign-in can run on iOS. Leave the backend value empty instead of using placeholder text until that client exists.

6. Final backend development environment

For current Android development, the relevant backend values are:

BACKEND_URL=http://localhost:5000
FRONTEND_URL=http://localhost:4200
GOOGLE_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-web-client-secret
GOOGLE_ANDROID_CLIENT_ID=your-android-client-id.apps.googleusercontent.com
GOOGLE_IOS_CLIENT_ID=

Restart the Node.js backend after editing .env. Hot reload of the Flutter app does not reload backend authentication configuration.

7. Configure Android Studio

  1. Open Run > Edit Configurations.

  2. Select or create the Roadly Customer Flutter configuration.

  3. Set Dart entrypoint to:

    <roadly-mobile-root>\apps\roadly_customer\lib\main.dart
    

    Replace <roadly-mobile-root> with the absolute path where this repository is cloned.

  4. Put the following on one line in Additional run args:

    --dart-define=ROADLY_API_URL=http://10.0.2.2:5000 --dart-define=GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
    
  5. Select the Android emulator, click Apply, and then click OK.

  6. Stop any currently running app and launch it again. Hot reload does not update compile-time Dart defines.

Do not put these values in the run configuration's Environment variables field. They are Flutter command-line build defines.

8. Run from a terminal

From the monorepo root:

flutter run `
  -t apps/roadly_customer/lib/main.dart `
  --dart-define=ROADLY_API_URL=http://10.0.2.2:5000 `
  --dart-define=GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com

The driver app does not need a Google client define:

flutter run `
  -t apps/roadly_driver/lib/main.dart `
  --dart-define=ROADLY_API_URL=http://10.0.2.2:5000

9. Troubleshooting Google sign-in

If account selection closes without signing in, confirm all of the following:

  • The mobile Web client ID exactly matches backend GOOGLE_CLIENT_ID.
  • The Android OAuth client uses com.roadly.roadly_customer.
  • The Android OAuth client contains the SHA-1 for the active build variant.
  • The Google account is listed under test users while OAuth publishing status is Testing.
  • The backend was restarted after .env changes.
  • The Flutter app was fully stopped and relaunched after changing Dart defines.
  • The emulator can reach http://10.0.2.2:5000.
  • Placeholder strings such as replace-with-google-ios-client-id are not left in active backend configuration.

Google Cloud changes can take several minutes to propagate.

Architecture

The applications use feature-first MVVM with repositories and services:

feature/
├── data/
│   ├── models/
│   ├── repositories/
│   └── services/
├── domain/
│   ├── entities/
│   └── use_cases/
└── presentation/
    ├── screens/
    ├── view_models/
    └── widgets/

Shared package responsibilities:

Package Responsibility
roadly_api HTTP contracts, serialization and structured API failures
roadly_auth Better Auth mobile session and role contracts
roadly_core Environment, result and cross-cutting primitives
roadly_design_system Themes, tokens and approved UI components
roadly_localization Typed EN, MS, ES and PT translations
roadly_maps GPS permission, native geocoding and private coordinate-picker contracts
roadly_notifications Customer and driver mobile push contracts
roadly_realtime Authenticated realtime connection contracts
roadly_support Shared customer and driver human-support workflow
roadly_testing Shared fixtures and test helpers

Mandatory UI and form policy

Feature screens must use components exported by roadly_design_system. Do not independently restyle Material buttons, fields, selects, cards, dialogs or status treatments inside a feature. If a component is missing, add and approve it in roadly_catalog first.

All application forms use reactive_forms through Roadly form components. Validation appears below its field, invalid submissions are disabled, and an in-flight action cannot be submitted twice.

Every shared component must cover light and dark themes, loading, disabled, focus, validation, translated text and accessible text scaling where relevant.

Current status

Completed foundation:

  • Customer, driver and component-catalog applications
  • Dart workspace and Melos configuration
  • Riverpod and GoRouter application shells
  • Shared package boundaries
  • Roadly light and dark themes
  • Roadly spacing and colour tokens
  • Initial button, icon button, badge, card, reactive input, select and state components
  • Typed English, Malay, Spanish and Portuguese catalogs
  • Initial package and widget tests
  • Better Auth signed Bearer sessions alongside the existing Angular cookies
  • Secure native session storage using the platform keychain or keystore
  • Shared HTTP transport with structured API failures and authorization headers
  • Role-aware customer and driver routing with session restoration and sign-out
  • Shared typed-reactive sign-in UI with validation and password visibility
  • Customer-only native Google ID-token sign-in through Better Auth, including secure token persistence and phone recovery when Google has no phone number
  • Authenticated Socket.IO transport for customer and driver apps using the stored Better Auth Bearer session, WebSocket-only native transport, reconnect lifecycle states, session-expiry sign-out, and a resync signal for recovering domain state missed while offline
  • Live customer vehicle discovery backed by Roadly's server-paginated catalog, with pull-to-refresh, incremental loading, vehicle imagery, availability, pricing, ratings, and localized empty/error/detail states
  • Live driver operations backed by the existing profile, availability, assignment, accept, and reject endpoints, including required structured rejection reasons, duplicate-action protection, and realtime refetches
  • Customer rental checkout with server availability, pricing, booking, cancellation, Stripe checkout, and combined Activity history
  • Customer Kuala Lumpur rides with typed fare quotes, cash/card request flow, card authorization, active-ride recovery, cancellation, realtime refresh, and ride history
  • Customer ride pickup and drop-off selection using landmark/street search, current device location, a private coordinate picker, localized permission recovery, and KL offline fallbacks. Selected coordinates pass through the existing signed server quote contract without client-side fare calculation.
  • Rental and ride Stripe checkout now persist a non-secret pending resource, return through the registered roadly://payment/... Android/iOS scheme, and refresh authenticated server state on return, resume, or app restart. Redirect parameters never mark a payment successful; Stripe webhooks and the refreshed booking/ride records remain authoritative.
  • Driver trip lifecycle controls backed by the server state machine, including restart-safe offer acceptance and required cash-receipt confirmation
  • Driver Activity, earnings and payout views using server pagination and server-calculated settlement values
  • Driver performance, compliance-document submission and safety incident reporting through typed APIs and reactive Roadly forms
  • Persisted EN/MS/ES/PT language, MYR/USD/EUR/GBP/SGD/AUD display-currency, and system/light/dark appearance preferences shared by customer and driver apps through roadly_core and roadly_design_system.
  • Customer catalog, rental availability, rental booking, and ride quotes send the selected display currency to the backend. Conversion uses Roadly's live-rate provider with configured fallback rates; settlement and historical transaction currencies remain unchanged.
  • Customer and driver apps share one account-secured Support Centre with typed reactive request/reply forms, related rental and ride context, resolved-chat protection, reopening, pull-to-refresh and localized inbox states.
  • Customer Home now surfaces live fleet, rental and active-ride information, quick booking actions and human support. Driver Drive provides immediate Activity, Earnings and Support shortcuts without changing its shift or trip state machine.

Remaining implementation phases are saved in docs/implementation-checkpoint.md:

  1. Complete the final visual, accessibility, regression and store-release gate.

Launcher icon and native splash

The customer and driver apps share the Roadly launcher identity: Roadly Night, Lime and Teal with the fleet mark kept inside Android's adaptive-icon safe zone. The same mark appears on a Roadly Night native splash while Flutter loads, including Android 12+ system splash support. Source artwork is stored in assets/branding/; generated Android and iOS assets are committed so a normal app build does not require an icon generator.

After changing the SVG source, regenerate every platform size from the repository root:

.\tool\generate_launcher_icons.ps1

The script uses installed Chrome or Edge to rasterize the SVG master and does not alter the internal catalog app.

Stripe mobile return testing

Password recovery

Customer and driver sign-in screens share the recovery forms from packages/roadly_auth. Requests always show the same check-your-email state, new passwords must meet Roadly's strength rules, and a successful reset invalidates existing sessions. The customer app registers roadly://auth/reset-password; the driver app registers roadly-driver://auth/reset-password on Android and iOS.

For local testing without Resend, submit the mobile forgot-password form and copy the callback URL printed by the backend terminal. Test Android links directly with:

adb shell am start -a android.intent.action.VIEW -d "roadly://auth/reset-password?token=TOKEN"
adb shell am start -a android.intent.action.VIEW -d "roadly-driver://auth/reset-password?token=TOKEN"

Production email delivery requires backend RESEND_API_KEY and EMAIL_FROM values. Store releases should replace custom schemes with verified Android App Links and iOS Universal Links when the production Roadly domain is available.

The backend .env controls the HTTPS/HTTP bridge used by Stripe:

# Android emulator
MOBILE_PAYMENT_RETURN_URL=http://10.0.2.2:5000/api/v1/payments/mobile-return

Use the deployed HTTPS backend URL in production. For a physical device during local development, use a reachable LAN IP or HTTPS development tunnel. Android and iOS register roadly://payment; no Stripe secret or session identifier is stored in the URL or trusted by the app.

You can exercise the Android deep link without Stripe:

adb shell am start -a android.intent.action.VIEW -d "roadly://payment/booking/success?id=1"

The signed-in customer must own the referenced booking. Roadly navigates to Activity and reloads the authenticated booking API. Use roadly://payment/ride/success?id=1 for a ride; cancellation uses cancelled instead of success.

Customer ride location setup

The customer app asks for location only after the user selects Use my current location. Android declares coarse and fine foreground location; iOS declares NSLocationWhenInUseUsageDescription. Background location is not requested.

Address search and reverse geocoding use the operating system's native geocoder. The Roadly coordinate preview does not load third-party map tiles or send a precise coordinate to a tile host. A production road-map provider can later implement the roadly_maps gateway after its privacy, billing and key restrictions are approved; the ride API and signed fare contract will not need to change.

See docs/mobile-api-readiness.md for the backend audit and required changes before authentication implementation.

About

Roadly Mobile is a Flutter monorepo containing separate customer and driver applications backed by shared, tested Roadly packages.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages