Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
8212a42
Preserve verified driver identity across route lookup
OziinG May 19, 2026
25ad82a
Keep auth forms usable around Galaxy keyboard chrome
OziinG May 19, 2026
f70ce35
Show drivers upcoming route work beyond today
OziinG May 19, 2026
21922a3
Make driver navigation match the next app IA
OziinG May 19, 2026
a9a046f
Keep driver tab server follow-up as an app-owned handoff
OziinG May 19, 2026
4d161aa
Align driver app semantics before native evidence
OziinG May 21, 2026
961b8d2
Make driver invite failures diagnosable without exposing secrets
OziinG Jun 1, 2026
5292d7c
Keep assigned routes refreshable in the driver app
OziinG Jun 1, 2026
f33e286
Merge pull request #106 from EVNSolution/feat/driver-app-tabs-ia
clever-deploy-bot Jun 1, 2026
04df7f7
Let drivers continue when routes are absent
OziinG Jun 1, 2026
497dfc5
Merge pull request #107 from EVNSolution/fix-driver-phone-login-witho…
clever-deploy-bot Jun 1, 2026
ccfeae2
Keep driver app CI audit actionable
OziinG Jun 1, 2026
0643a4f
Merge pull request #108 from EVNSolution/fix-driver-app-audit-overrides
clever-deploy-bot Jun 1, 2026
8baa0d4
Align driver app Expo patch
OziinG Jun 1, 2026
c258320
Merge pull request #109 from EVNSolution/fix-driver-app-expo-patch-al…
clever-deploy-bot Jun 1, 2026
66fc2ce
Accept current route choice envelopes without breaking login (#110)
clever-deploy-bot Jun 1, 2026
3498f42
Keep unfinished driver routes grounded in server truth
OziinG Jun 2, 2026
61e18cc
Surface assigned-route OSRM evidence in the driver app
OziinG Jun 4, 2026
745eea4
Enable internal driver route smoke sessions
OziinG Jun 4, 2026
b42b02c
Merge branch 'cc-222-driver-route-osrm' into cc-221-unfinished-routes…
OziinG Jun 4, 2026
6b304cf
Keep Android route work recoverable through native back actions
OziinG Jun 4, 2026
cfc45c5
Keep driver progress explicit before native validation
OziinG Jun 4, 2026
85babd1
Keep driver release candidate compatible with main
OziinG Jun 4, 2026
05b557a
Merge pull request #112 from EVNSolution/cc-221-unfinished-routes-truth
clever-deploy-bot Jun 4, 2026
246a56e
Show drivers canonical Woo payment status before collection
OziinG Jun 5, 2026
b3ce49b
Show canonical Woo payment status in driver app
clever-deploy-bot Jun 5, 2026
be3b377
Show server route previews in the driver app
OziinG Jun 11, 2026
efdd9a3
Show static route previews before route start
OziinG Jun 11, 2026
48d18c4
Keep route preview fully visible
OziinG Jun 11, 2026
4dceda4
Stabilize home route restoration
OziinG Jun 11, 2026
8dc403d
Render route previews on native interactive maps
OziinG Jun 11, 2026
cbe64c5
Gate route start behind depot pickup confirmation
OziinG Jun 11, 2026
64329d0
Let live tracking scroll between map and details
OziinG Jun 11, 2026
cbd3004
Clarify live tracking as stacked route cards
OziinG Jun 11, 2026
170fd65
Animate live tracking as a covering card stack
OziinG Jun 11, 2026
d76d931
Reveal live map by sliding the details card
OziinG Jun 11, 2026
17d78ed
Capture live tracking vertical card gestures
OziinG Jun 11, 2026
4e9c53d
Make live tracking details-first with map preview
OziinG Jun 11, 2026
6661e15
Move live map preview into the details card
OziinG Jun 11, 2026
c003988
Make live map preview read as a CTA
OziinG Jun 11, 2026
825b616
Remove unstable map preview glyph
OziinG Jun 11, 2026
7e66f7b
Remove Android shadow from map preview CTA
OziinG Jun 11, 2026
8a44c68
Relax live tracking card top spacing
OziinG Jun 11, 2026
a970401
Lower app content from the status bar
OziinG Jun 11, 2026
7d17c3a
Show depot marker on route preview maps
OziinG Jun 11, 2026
1174ec9
Make route detail the driver work hub
OziinG Jun 11, 2026
9b61e90
Align route start copy with pickup work
OziinG Jun 11, 2026
3bcbef2
Keep route progress on one primary action
OziinG Jun 11, 2026
d9a1816
Route stop arrivals through notifications
OziinG Jun 11, 2026
735af1d
Tighten stop arrival alert radius
OziinG Jun 11, 2026
5394c41
Detail server work for stop arrival FCM
OziinG Jun 11, 2026
1a67019
Reduce local Expo doctor noise
OziinG Jun 15, 2026
6e6baa6
Keep the driver app on the current Expo SDK
OziinG Jun 15, 2026
bf0abc1
Align route entry with recoverable sessions
OziinG Jun 15, 2026
b284b95
Separate route preview from session work
OziinG Jun 15, 2026
54e26b0
Clarify route preview area and stop order
OziinG Jun 15, 2026
f6ebf5f
Prevent route preview region truncation
OziinG Jun 15, 2026
0611018
Render route preview regions as compact list
OziinG Jun 15, 2026
717e52d
Remove area text from route preview sequence
OziinG Jun 15, 2026
4eeb620
Put active work actions in current task
OziinG Jun 15, 2026
9d987bc
Keep stop details informational
OziinG Jun 15, 2026
5639330
Calm current task emphasis
OziinG Jun 15, 2026
557179e
Simplify route sequence addresses
OziinG Jun 15, 2026
ac425ac
Stop automatic driver app CI runs
OziinG Jun 15, 2026
bfbed4a
Emphasize only the current route sequence row
OziinG Jun 15, 2026
0058c33
Make stop completion proof copy driver-facing
OziinG Jun 15, 2026
efdfbe0
Keep delivery photo capture quiet until completion
OziinG Jun 15, 2026
74fb634
Collapse delivery photo entry to one action
OziinG Jun 15, 2026
a7b8fd4
Use app-styled photo source sheet
OziinG Jun 15, 2026
1bc3e54
Keep app bottom sheets above Android navigation
OziinG Jun 15, 2026
3512cdd
Tune photo sheet action colors compactly
OziinG Jun 15, 2026
a4e6391
Normalize photo sheet button density
OziinG Jun 15, 2026
794649d
Fix photo sheet button height parity
OziinG Jun 15, 2026
b0cbac6
Share one driver app icon asset
OziinG Jun 16, 2026
34c62ca
Shrink the app by cutting unused barcode capture
OziinG Jun 16, 2026
051931e
Lower Android bottom tabs to the system bar
OziinG Jun 16, 2026
7fa6625
Let Android place bottom tabs at the system bar
OziinG Jun 16, 2026
10818e7
Keep preview maps honest about route geometry
OziinG Jun 16, 2026
8c6ccad
Merge pull request #119 from EVNSolution/cc-driver-app-android-bottom…
clever-deploy-bot Jun 16, 2026
e7c4663
Merge pull request #118 from EVNSolution/cc-driver-app-ponytail-audit…
clever-deploy-bot Jun 16, 2026
20a0d5e
Merge pull request #120 from EVNSolution/cc-224-map-preview-performance
clever-deploy-bot Jun 16, 2026
2244023
Make driver identity reusable across route assignments
OziinG Jul 14, 2026
d5621ca
Keep the release gate aligned with patched Expo dependencies
OziinG Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
name: Driver App CI

# Run manually only to avoid consuming GitHub Actions minutes on every PR/push.
on:
pull_request:
branches:
- dev
- main
push:
branches:
- dev
- main
workflow_dispatch:

concurrency:
group: driver-app-ci-${{ github.workflow }}-${{ github.ref }}
Expand Down
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Dedicated native mobile app repository for Clever delivery drivers.

## MVP direction

1. Driver selects a supported country and enters the dispatch-registered phone number in national format.
2. Driver confirms the company/shop/route guidance for the assigned work.
1. Driver selects a supported country, enters the dispatch-registered phone number, and signs in with a six-digit PIN.
2. A first-time driver uses an existing Shopify invitation code once and creates the six-digit PIN; the app does not create or request the invitation.
3. Driver accepts location-information and personal-information consent.
4. Driver views the route assigned for the current delivery day.
4. Driver confirms the company/shop/route guidance and views assigned work.

## Platform direction

Expand All @@ -19,14 +19,16 @@ Dedicated native mobile app repository for Clever delivery drivers.

- `clever-delivery-server` is the source of truth for companies/shops, drivers, routes, orders, assignments, consent records, and location/compliance logs.
- This repository owns the driver-facing mobile UX, runtime, local verification, and mobile release evidence.
- Phone number alone must not be treated as a global driver identity; route/company context is part of the access boundary.
- The delivery server owns one global driver account per normalized E.164 phone number. Phone possession alone is not authentication: existing accounts require the PIN, while first registration additionally requires an existing invitation code.
- Shopify invitation/signup creation is administrator- and user-managed. This app does not send or automate that request.

## Current app flow

The app includes an interactive route access screen:

- country-aware phone entry with a broad supported-country catalog, locale/language/culture metadata, national display formatting, and E.164 normalization before lookup
- local mock service for delivery-server `POST /driver/route-access/lookup`
- country-aware phone entry with a broad supported-country catalog, national display formatting, and E.164 normalization before account authentication
- phone + six-digit PIN login for existing accounts; first-registration mode adds the existing six-character invitation code and PIN confirmation without collecting a driver name
- separate server-issued account and route access tokens, with account-authenticated `POST /driver/route-access/lookup`
- company/shop/route guidance for `INVITED`, plus safe multi-company ambiguity guidance that does not expose route/stop/customer data or driver access tokens
- local proof-media upload smoke selector for success, retryable failure, and scanner rejection while the app is using mock services
- app-side consent gate for required location-information and personal-information consent
Expand All @@ -35,9 +37,9 @@ The app includes an interactive route access screen:
- assigned route mock/API boundary for delivery-server `GET /driver/assigned-route`
- route summary, ordered stop cards, and OS map handoff after consent moves the flow to `route_ready`
- explicit delivery start action that requests OS foreground location permission before `delivery_active`
- route started, foreground one-shot location update, continuous background-capable location streaming, native proof photo URI capture, proof media upload references, scanner-rejected proof photo recapture guidance, signature/barcode proof capture, richer stop delivered/failed proof-event mock/API boundaries, and route-completed delivery finish cleanup for delivery-server `POST /driver/events` after delivery_active succeeds
- route started, foreground one-shot location update, continuous background-capable location streaming, native proof photo URI capture, proof media upload references, scanner-rejected proof photo recapture guidance, signature proof capture, richer stop delivered/failed proof-event mock/API boundaries, and route-completed delivery finish cleanup for delivery-server `POST /driver/events` after delivery_active succeeds
- safe denial messages for `NOT_FOUND`, `DISABLED`, and `BLOCKED`
- live `EXPO_PUBLIC_DELIVERY_SERVER_BASE_URL` switch for route lookup, native secure storage for the short-lived driver token, live downstream consent/assigned-route/driver-event/proof-media clients with no-store/no-cookie request options, and live `401` recovery that clears the active driver token and returns the driver to country-aware phone lookup; durable app-side offline queue/retry for pending driver events and proof media using AsyncStorage-backed non-secret queue metadata, with app-side discard thresholds for repeated failure, stale age, recorded route completion cleanup, explicit scanner-rejected proof media discard/recapture handling, and an explicit driver session reset action that clears secure access plus queued retry state; delivery-server local/manual proof-media cleanup support and server-side scan rejection hook; production proof-media object storage, signed access, deployed scanner evidence, deployed cleanup evidence, physical-device smoke evidence, and store/privacy disclosure evidence left for follow-up slices
- live `EXPO_PUBLIC_DELIVERY_SERVER_BASE_URL` switch for account authentication and route lookup, native secure storage with account and route credentials kept separate, account refresh plus one route-token retry on downstream `401`, and authoritative removal of cached routes when assignments disappear; durable app-side offline queue/retry for pending driver events and proof media using AsyncStorage-backed non-secret queue metadata, with explicit reset and cleanup behavior

See `docs/route-access-flow.md` for the app-side route access, consent, assigned-route, native map handoff, and delivery evidence boundary.

Expand All @@ -60,7 +62,7 @@ npm install
npm run start
```

Optional live API mode (route lookup saves the returned short-lived driver token through Expo SecureStore, clears expired/invalid persisted tokens before reuse, and clears live downstream `401` tokens before requiring country-aware phone lookup again; see `.env.example`):
Optional live API mode (account login/registration stores server-issued account access and refresh credentials separately from route-scoped driver access; expired route access is reacquired through account refresh and route lookup, while an expired account session returns to phone + PIN login; see `.env.example`):

```bash
EXPO_PUBLIC_DELIVERY_SERVER_BASE_URL=https://delivery.example.com npm run start
Expand Down Expand Up @@ -89,8 +91,7 @@ npx expo install --check
git diff --check
```

GitHub Actions runs the PR/push subset in `.github/workflows/ci.yml` for
`dev` and `main`: dependency install, workspace checks, lint, native release
GitHub Actions provides a manually dispatched verification workflow in `.github/workflows/ci.yml` to avoid consuming minutes on every PR/push: dependency install, workspace checks, lint, native release
preflight, non-secret release evidence seed rendering, app bundle export, npm
audit, Expo dependency alignment, and diff whitespace checks. CI does not run
EAS binary builds and does not replace Apple/Google signing, store/private
Expand Down
19 changes: 19 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# OSX
#
.DS_Store

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/

# generated inline modules
app/src/main/java/inline/

# Bundle artifacts
*.jsbundle
182 changes: 182 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"

def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()

/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
hermesCommand = new File(["node", "--print", "require.resolve('hermes-compiler/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/hermesc/%OS-BIN%/hermesc"
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()

enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
// Use Expo CLI to bundle the app, this ensures the Metro config
// works correctly with Expo projects.
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
bundleCommand = "export:embed"

/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
// codegenDir = file("../../node_modules/@react-native/codegen")

/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]

/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]

//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []

/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]

/* Autolinking */
autolinkLibrariesWithApp()
}

/**
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
*/
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()

/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'

android {
ndkVersion rootProject.ext.ndkVersion

buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion

namespace 'com.evns.cleverdriverapp'
defaultConfig {
applicationId 'com.evns.cleverdriverapp'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"

buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
shrinkResources enableShrinkResources.toBoolean()
minifyEnabled enableMinifyInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
crunchPngs enablePngCrunchInRelease.toBoolean()
}
}
packagingOptions {
jniLibs {
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
useLegacyPackaging enableLegacyPackaging.toBoolean()
}
}
androidResources {
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}

// Apply static values from `gradle.properties` to the `android.packagingOptions`
// Accepts values in comma delimited lists, example:
// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop ->
// Split option: 'foo,bar' -> ['foo', 'bar']
def options = (findProperty("android.packagingOptions.$prop") ?: "").split(",");
// Trim all elements in place.
for (i in 0..<options.size()) options[i] = options[i].trim();
// `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings.
options -= ""

if (options.length > 0) {
println "android.packagingOptions.$prop += $options ($options.length)"
// Ex: android.packagingOptions.pickFirsts += '**/SCCS/**'
options.each {
android.packagingOptions[prop] += it
}
}
}

dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")

def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";

if (isGifEnabled) {
// For animated gif support
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
}

if (isWebpEnabled) {
// For webp support
implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
if (isWebpAnimatedEnabled) {
// Animated webp support
implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
}
}

if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
14 changes: 14 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# react-native-reanimated
-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }

# Add any project specific keep options here:
7 changes: 7 additions & 0 deletions android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
</manifest>
7 changes: 7 additions & 0 deletions android/app/src/debugOptimized/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
</manifest>
Loading