Replace the customer app with Doormile CX
Book a pickup, track it to delivery — the rebuilt customer app. - Design language from doormile-screens.html: brand #8F0F06, Manrope + Geist Mono (variable fonts), bordered cards instead of shadows, crimson brand headers, sliding tab indicator, mono for anything read digit by digit. - lib/data (one live API implementation, plus a debug-only offline fake), lib/state, lib/ui (tokens, widgets, screens). - 84 tests, plus a design snapshot harness that renders every screen with the real fonts: flutter test test/design_snapshot_test.dart --run-skipped --update-goldens This replaces the previous app (pubspec 'doormile', app id com.doormile.customer). That tree remains in history at 6c7d656; note its android/app/google-services.json is not carried over, and the application id here is in.doormile.customer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8
.gitignore
vendored
@@ -43,3 +43,11 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Widget Preview related
|
||||
.widget_preview/
|
||||
|
||||
# Android signing — never commit these
|
||||
android/key.properties
|
||||
**/*.jks
|
||||
**/*.keystore
|
||||
|
||||
21
.metadata
@@ -4,7 +4,7 @@
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "924134a44c189315be2148659913dda1671cbe99"
|
||||
revision: "d3b14c876900e553bc736ca19295fc09e3853e8e"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
@@ -13,20 +13,17 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
- platform: android
|
||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
- platform: ios
|
||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
- platform: macos
|
||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
- platform: web
|
||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
||||
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
364
README.md
@@ -1,63 +1,321 @@
|
||||
# Doormile — EV-First Parcel Delivery (Flutter)
|
||||
# Doormile — Customer App
|
||||
|
||||
A complete, fully-navigable Flutter app built from the Doormile design brief.
|
||||
EV-first delivery, MileTruth AI routing narrative, corporate-red design system.
|
||||
A Flutter app for the Doormile customer journey:
|
||||
|
||||
## What's inside (all screens implemented)
|
||||
**Book pickup → Track → Delivered**
|
||||
|
||||
| Flow | Screens |
|
||||
|------|---------|
|
||||
| Onboarding | Splash → 3-slide Onboarding |
|
||||
| Auth | Login (phone) → OTP verify |
|
||||
| Home | Home hub (hero, quick actions, active shipment, eco banner) |
|
||||
| Booking | Pickup/Drop → Parcel details & slot → Service & price → Review → Payment sheet → Success |
|
||||
| Tracking | My Parcels (filters) → Live Tracking (map + rider) → Shipment Journey timeline |
|
||||
| Wallet | Balance card, add money, saved methods, transactions |
|
||||
| Profile | Stats, settings list, logout |
|
||||
| Support | FAQ accordion + chat assistant |
|
||||
|
||||
Everything is wired with real navigation and in-memory state (Provider):
|
||||
placing an order adds it to **My Parcels**, paying by wallet deducts the balance,
|
||||
"Add money" updates the wallet, logout returns to login, etc.
|
||||
|
||||
## Design system
|
||||
|
||||
- **Colours / type / spacing** mirror `DESIGN.md` (primary `#960019`, Public Sans).
|
||||
- Public Sans is **bundled** (`assets/fonts/PublicSans.ttf`) — no network fonts.
|
||||
- Reusable widgets in `lib/widgets/` (cards, buttons, chips, progress bars).
|
||||
|
||||
## How to run
|
||||
|
||||
Open the Terminal app, then (the `flutter` tool lives in Homebrew):
|
||||
|
||||
```bash
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
cd /Users/apple/Desktop/Doormile-app/doormile
|
||||
|
||||
# Pick ONE target:
|
||||
flutter run -d chrome # opens in Chrome (fastest, no setup)
|
||||
flutter run -d macos # native macOS window
|
||||
flutter run # phone/emulator if one is connected
|
||||
```
|
||||
|
||||
Demo tips: at login enter **any 10-digit number**, and at OTP enter **any 6 digits** —
|
||||
there's no backend, it's a front-end prototype.
|
||||
|
||||
## Project layout
|
||||
State is a `ChangeNotifier` behind an `InheritedNotifier`; navigation is
|
||||
`Navigator` with named booking steps. Real maps come from `flutter_map` over
|
||||
OpenStreetMap/CARTO tiles and real positions from `geolocator` — no API key and
|
||||
no billing account required. Everything else is the Flutter SDK.
|
||||
|
||||
```
|
||||
lib/
|
||||
main.dart # app entry, theme, named routes
|
||||
theme/ # colours + text styles (design tokens)
|
||||
models/ # Order, AddressEntry, WalletTransaction, ...
|
||||
state/app_state.dart # Provider store + mock data + booking draft
|
||||
widgets/ # shared UI (AppCard, PrimaryButton, StatusChip, logo)
|
||||
screens/ # one file per screen, grouped by flow
|
||||
test/ # smoke, full-journey, and per-screen render tests
|
||||
main.dart app entry, theme, page transitions
|
||||
data/
|
||||
models.dart Booking, Destination, DeliveryDetails, JourneyStage…
|
||||
doormile_api.dart the service surface — one implementation
|
||||
live_doormile_api.dart ← the ONLY place the app talks to a server
|
||||
location_service.dart ← the ONLY place device GPS is touched
|
||||
map_config.dart ← the ONLY place tile URLs, attribution + keys live
|
||||
state/
|
||||
app_state.dart single source of truth + stage transitions
|
||||
app_scope.dart InheritedNotifier plumbing
|
||||
ui/
|
||||
tokens.dart colours, radii, type scale, shadows (Inter)
|
||||
format.dart greeting / relative time / date labels
|
||||
widgets/ buttons, chrome, inputs, option tiles, milestones…
|
||||
map_tiles.dart config → flutter_map layer, + attribution
|
||||
map_panel.dart real pannable map, pin locked to the centre
|
||||
route_map.dart real route map: polyline, hubs, live Miler
|
||||
screens/
|
||||
auth/ login · sign up · OTP (shared brand scaffold)
|
||||
shell_screen.dart Home · Orders · Account bottom navigation
|
||||
home_screen.dart orders_screen.dart account_screen.dart
|
||||
order_details_screen.dart receipt for a finished order
|
||||
tracking_screen.dart live tracking, driven by stage
|
||||
booking/ pickup → destination → time → review → confirmed
|
||||
sheets/ place search, delivery details, cancel booking
|
||||
store/ Play Console listing assets
|
||||
```
|
||||
|
||||
## The product model this encodes
|
||||
|
||||
The customer creates a **pickup booking**, not a shipment.
|
||||
|
||||
Booking is four steps:
|
||||
|
||||
```
|
||||
Home ▸ Book a Pickup
|
||||
1. Pickup point map pin, pre-filled from the device location
|
||||
2. Destination state → district, then "How many packages?" (defaults to 1)
|
||||
"+ Send to another place" adds another destination
|
||||
3. Pickup time windows the backend still has capacity for
|
||||
4. Review one line per place, combined estimate, Book Pickup
|
||||
```
|
||||
|
||||
**One pickup booking → one or more destinations → order(s) created after
|
||||
collection.** A single Miler visit can collect packages bound for several
|
||||
districts; each destination becomes its own tracked order only once the Miler
|
||||
completes the pickup. The customer never sees carrier vocabulary — no
|
||||
consignment, no waybill, no AWB.
|
||||
|
||||
* **Required:** pickup location, and for each destination a serviceable
|
||||
**state** + **district**. Package count defaults to 1.
|
||||
* **Optional:** exact street, landmark, recipient name and phone, delivery
|
||||
instructions — per destination, in a sheet opened from the destination card
|
||||
or Review. Never blocking, and shown as
|
||||
*"Not added — the Miler can confirm this at pickup."*
|
||||
* **Weight is never asked.** The Miler verifies it at pickup, which is when the
|
||||
price settles.
|
||||
* **Caps** come from `BookingLimits` (backend-served, defaults 20 packages /
|
||||
5 destinations per pickup). Nothing in the UI hardcodes them.
|
||||
* **Cancellation** is whole-pickup only, and only while
|
||||
`DoormileApi.isCancellable(stage)` allows it.
|
||||
|
||||
A customer sending one package to one district sees exactly what they saw
|
||||
before: pick a place, tap Next. The multi-destination machinery appears only
|
||||
when they ask for it.
|
||||
|
||||
Stages shown to the customer (`JourneyStage`; raw backend keys never reach a
|
||||
screen):
|
||||
|
||||
```
|
||||
Pickup booked → Miler assigned → Miler on the way → Miler arrived →
|
||||
Package picked up → Order created → In transit → Out for delivery → Delivered
|
||||
```
|
||||
|
||||
**Cancellation** is offered only while `DoormileApi.isCancellable(stage)` is
|
||||
true — through *Miler arrived*. Once the Miler has custody the footer becomes a
|
||||
support action instead.
|
||||
|
||||
## Maps and location
|
||||
|
||||
Both maps are real. `flutter_map` renders OpenStreetMap data through CARTO's
|
||||
basemaps — no API key, no billing, no per-request cost to develop against.
|
||||
|
||||
**No widget knows a tile URL.** `lib/data/map_config.dart` is the only place a
|
||||
template, a subdomain list, an attribution string or a map API key exists;
|
||||
`lib/ui/widgets/map_tiles.dart` turns that into a layer. Changing provider is a
|
||||
build flag, never a map-screen edit — and a test fails the build if a URL ever
|
||||
reappears under `lib/ui/`.
|
||||
|
||||
* **Pickup** (`DmMapPanel`) — the pin is welded to the centre of the map and the
|
||||
map moves under it. When it settles, the coordinates under the pin are
|
||||
reverse-geocoded and the address appears in the box below, inside the same
|
||||
card. Drag, and the address follows.
|
||||
* **The customer's own position** is a blue dot with an accuracy halo drawn at
|
||||
true map scale, so it shrinks as they zoom in. It is a real coordinate, not a
|
||||
screen overlay: dragging moves the crimson pickup pin and leaves the dot
|
||||
where the customer actually is, which is how they judge how far they have
|
||||
moved the pin from themselves. No fix means no dot — a dot in the wrong place
|
||||
is worse than none.
|
||||
* **Route** (`DmRouteMap`) — pickup and destination hub as real markers joined
|
||||
by the transit arc, with the Miler's live position along it. Non-interactive
|
||||
on purpose: it summarises a booking and must never steal a scroll gesture.
|
||||
* **Position** (`LocationService`) — the only place `geolocator` is touched.
|
||||
Every platform call is bounded by a timeout and every failure comes back as a
|
||||
typed `LocationDenial`, so a refused permission or a wedged plugin can never
|
||||
freeze the booking flow. It falls back to the service-area centre and says
|
||||
why.
|
||||
|
||||
### Choosing a tile provider
|
||||
|
||||
CARTO is the development default and is *not* assumed to be the production
|
||||
contract. Everything is set at build time:
|
||||
|
||||
```bash
|
||||
# CARTO with a proper API key — the likely production step
|
||||
flutter build appbundle --dart-define=DM_MAP_KEY=$CARTO_API_KEY
|
||||
|
||||
# a different vendor
|
||||
flutter run --dart-define=DM_MAP_PROVIDER=maptiler --dart-define=DM_MAP_KEY=…
|
||||
flutter run --dart-define=DM_MAP_PROVIDER=stadia --dart-define=DM_MAP_KEY=…
|
||||
|
||||
# self-hosted or anything else
|
||||
flutter run \
|
||||
--dart-define=DM_MAP_PROVIDER=custom \
|
||||
--dart-define=DM_MAP_URL=https://tiles.doormile.in/streets/{z}/{x}/{y}.png \
|
||||
--dart-define=DM_MAP_URL_MUTED=https://tiles.doormile.in/light/{z}/{x}/{y}.png \
|
||||
--dart-define=DM_MAP_ATTRIBUTION='© Doormile · © OpenStreetMap contributors'
|
||||
```
|
||||
|
||||
| Define | Effect |
|
||||
| --- | --- |
|
||||
| `DM_MAP_PROVIDER` | `carto` (default) · `osm` · `maptiler` · `stadia` · `custom` |
|
||||
| `DM_MAP_KEY` | Appended as the provider's key parameter (`api_key`, `key`) |
|
||||
| `DM_MAP_URL` / `DM_MAP_URL_MUTED` | Templates for `custom` |
|
||||
| `DM_MAP_SUBDOMAINS` | Comma-separated `{s}` shards |
|
||||
| `DM_MAP_ATTRIBUTION` / `_URL` | Credit line and licence link |
|
||||
| `DM_MAP_MAX_ZOOM` | Deepest native zoom the provider serves |
|
||||
| `DM_APP_VERSION` | Goes into the `User-Agent` |
|
||||
|
||||
A provider that needs a key but was given none falls back to CARTO with a
|
||||
`warning` on the config rather than rendering a blank map. Every tile request
|
||||
carries `User-Agent: Doormile/<version> (in.doormile.customer)`, so the provider
|
||||
can attribute and rate-limit our traffic properly. `keepBuffer`/`panBuffer` stay
|
||||
at flutter_map's defaults — the app fetches the tiles it is about to draw and
|
||||
nothing more. There is no bulk prefetch or offline tile scraping, by design.
|
||||
|
||||
The credit line is rendered on **every** map, its text taken from whichever
|
||||
provider is configured, so it stays correct after a swap.
|
||||
|
||||
`ACCESS_FINE_LOCATION` / `ACCESS_COARSE_LOCATION` are declared on Android and
|
||||
`NSLocationWhenInUseUsageDescription` on iOS; GPS hardware is marked optional so
|
||||
Play does not filter the app off devices without it.
|
||||
|
||||
|
||||
## Running it
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
flutter run # any connected device or simulator
|
||||
flutter test # 79 tests covering the whole flow
|
||||
```
|
||||
|
||||
`flutter run` talks to `https://api.doormile.com/api/v1`. Point it somewhere
|
||||
else with a define:
|
||||
|
||||
```bash
|
||||
flutter run --dart-define=DM_API_BASE=https://<staging-host>/api/v1
|
||||
```
|
||||
|
||||
### Signing in for development
|
||||
|
||||
A shipping build has **one** API implementation, `LiveDoormileApi`, and it talks
|
||||
to a server. Nothing invented can reach a customer.
|
||||
|
||||
But the real backend has no SMS gateway yet, so the OTP never arrives and you
|
||||
cannot sign in against it. Two dev-only ways in, both `!kReleaseMode` — no define
|
||||
can put either in a release build, and the Account screen prints the mode it is
|
||||
in:
|
||||
|
||||
**Offline dev data** — the whole app, no server, any 4-digit code signs in:
|
||||
|
||||
```bash
|
||||
flutter run --dart-define=DM_MOCK=true
|
||||
# login screen → 9876543210 → 1234 → in.
|
||||
# add --dart-define=DM_DEV_LOGIN=true to skip the login screen entirely.
|
||||
```
|
||||
|
||||
This swaps in `DevDoormileApi`: in-memory serviceability, slots, fare and
|
||||
bookings, so you can walk signup → book → track without a backend. **A booking
|
||||
made here stays on the device — it reaches no server and no admin console.** The
|
||||
Account screen reads `DEV DATA (offline)`.
|
||||
|
||||
**Real token** — talk to the live server, skip only the OTP round trip. Not a
|
||||
bypass: the server authorises every request it signs, and a revoked token signs
|
||||
the build straight back out:
|
||||
|
||||
```bash
|
||||
flutter run --dart-define=DM_DEV_TOKEN=eyJ... \
|
||||
--dart-define=DM_DEV_REFRESH_TOKEN=...
|
||||
```
|
||||
|
||||
### Walking a booking through its stages
|
||||
|
||||
Against dev data, the tracking screen's stepper walks the in-memory booking.
|
||||
Against a non-production backend it drives the server's own QA helper
|
||||
(`POST /customer/ops/bookings/{ref}/stage`) — each tap posts and re-reads, so
|
||||
what appears is the stage the **server** recorded:
|
||||
|
||||
```bash
|
||||
flutter run --dart-define=DM_API_BASE=https://<staging-host>/api/v1 \
|
||||
--dart-define=DM_ALLOW_STAGE_OVERRIDE=true
|
||||
```
|
||||
|
||||
The backend gates it too, with `CX_ALLOW_STAGE_OVERRIDE`. The app refuses to
|
||||
send it on production regardless.
|
||||
|
||||
### Walking a booking through its stages
|
||||
|
||||
On a non-production backend, the tracking screen can offer a stepper that drives
|
||||
the server's own QA helper (`POST /customer/ops/bookings/{ref}/stage`). Each tap
|
||||
posts and then re-reads the booking, so what appears is the stage the **server**
|
||||
recorded:
|
||||
|
||||
```bash
|
||||
flutter run --dart-define=DM_API_BASE=https://<staging-host>/api/v1 \
|
||||
--dart-define=DM_ALLOW_STAGE_OVERRIDE=true
|
||||
```
|
||||
|
||||
The backend gates it too, with `CX_ALLOW_STAGE_OVERRIDE`. The app refuses to
|
||||
send it on production regardless.
|
||||
|
||||
## The backend
|
||||
|
||||
Every screen's data comes from `lib/data/live_doormile_api.dart`, which is a
|
||||
thin translation between the app's models and the wire contract — the transport
|
||||
concerns (envelope, error codes, bearer auth and its silent refresh,
|
||||
idempotency, `ETag`, keyset paging, retries) all live in `api_client.dart`.
|
||||
|
||||
`DmAsyncList` wraps every backend-driven list, so loading skeletons, empty
|
||||
states and retry-on-error come with any new endpoint for free.
|
||||
|
||||
`District` carries `lat`/`lng` for its hub, which is what the route map draws
|
||||
to; without them it simply draws the pickup end.
|
||||
|
||||
Still to wire: push notifications (`POST /customer/devices` is implemented but
|
||||
no SDK produces a token), deep links (`GET /customer/orders/{trackingId}` is
|
||||
implemented but no intent filter or URL type is registered), and a post-booking
|
||||
edit screen for `PATCH …/destinations/{i}`. The contract is in
|
||||
[`docs/API_READINESS.md`](docs/API_READINESS.md).
|
||||
|
||||
## Releasing to Google Play
|
||||
|
||||
The app is configured for a Play upload: application ID `in.doormile.customer`,
|
||||
label **Doormile**, portrait only, `INTERNET` declared in the main manifest,
|
||||
adaptive + monochrome launcher icon, an Android 12+ splash screen, R8 shrinking
|
||||
on release, and backup/device-transfer disabled.
|
||||
|
||||
1. **Create the upload key** (once):
|
||||
|
||||
```bash
|
||||
keytool -genkey -v -keystore android/upload-keystore.jks \
|
||||
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
|
||||
```
|
||||
|
||||
2. **Point Gradle at it**: copy `android/key.properties.example` to
|
||||
`android/key.properties` and fill in the four values. Both the keystore and
|
||||
`key.properties` are git-ignored — never commit them. Without this file the
|
||||
release build falls back to debug signing so `flutter run --release` keeps
|
||||
working locally.
|
||||
|
||||
3. **Set the version** in `pubspec.yaml` (`version: 1.0.0+1` → `versionName` +
|
||||
`versionCode`); bump the build number for every upload.
|
||||
|
||||
4. **Build the bundle**:
|
||||
|
||||
```bash
|
||||
flutter build appbundle --release
|
||||
# build/app/outputs/bundle/release/app-release.aab
|
||||
```
|
||||
|
||||
5. **Upload** to Play Console. Listing assets are in `store/`:
|
||||
`play_store_icon_512.png` and `play_feature_graphic_1024x500.png`. You still
|
||||
need phone screenshots, a short/full description, a privacy policy URL, the
|
||||
data-safety form (this build collects nothing — no analytics, no third-party
|
||||
SDKs) and a content rating.
|
||||
|
||||
A JDK 17 is required for Android builds (`flutter doctor` will confirm) — the
|
||||
one bundled with Android Studio works:
|
||||
`flutter config --jdk-dir "/Applications/Android Studio.app/Contents/jbr/Contents/Home"`.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
flutter test # 18 tests: boot, full user journey, every screen renders clean
|
||||
```
|
||||
The test suite is the only place a fake backend exists:
|
||||
`test/support/fake_doormile_api.dart`, injected through
|
||||
`DoormileApi.overrideInstance`. It owns its bookings and its stage machine
|
||||
exactly as the server does — no test reaches into `AppState` to plant one.
|
||||
|
||||
`test/live_api_wire_test.dart` checks what the app actually puts on the wire,
|
||||
field by field, against the customer API document.
|
||||
|
||||
`test/booking_flow_test.dart` drives the app end to end: signing in through the
|
||||
OTP screen; dragging the pickup map and watching the address re-resolve; a
|
||||
refused location falling back without blocking the booking; booking one package to one district with every optional field
|
||||
skipped; four packages to one place; three destinations with mixed counts and
|
||||
the review total; the package cap and destination cap; state→district
|
||||
serviceability filtering; packages staying together until collection and then
|
||||
splitting into their own orders; cancelling; a delivered order's receipt; and
|
||||
the empty/error/retry service-area states.
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
- web/**
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import java.util.Properties
|
||||
import java.io.FileInputStream
|
||||
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
id("com.google.gms.google-services")
|
||||
}
|
||||
|
||||
// Upload-key credentials live outside version control. Copy
|
||||
// `android/key.properties.example` to `android/key.properties` and fill it in
|
||||
// before building a release bundle; without it we fall back to debug signing so
|
||||
// `flutter run --release` still works on a developer machine.
|
||||
val keystoreProperties = Properties().apply {
|
||||
val file = rootProject.file("key.properties")
|
||||
if (file.exists()) {
|
||||
file.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
val hasUploadKey = keystoreProperties.getProperty("storeFile") != null
|
||||
|
||||
android {
|
||||
namespace = "com.doormile.customer"
|
||||
namespace = "in.doormile.customer"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
@@ -26,33 +29,46 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.doormile.customer"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
applicationId = "in.doormile.customer"
|
||||
minSdk = 24
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
||||
keyPassword = keystoreProperties["keyPassword"] as String?
|
||||
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
|
||||
storePassword = keystoreProperties["storePassword"] as String?
|
||||
if (hasUploadKey) {
|
||||
create("release") {
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
storeFile = rootProject.file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = if (keystorePropertiesFile.exists()) {
|
||||
signingConfig = if (hasUploadKey) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
}
|
||||
}
|
||||
|
||||
bundle {
|
||||
language { enableSplit = false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +81,3 @@ kotlin {
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Import the Firebase BoM
|
||||
implementation(platform("com.google.firebase:firebase-bom:34.14.1"))
|
||||
// Add the dependencies for Firebase products you want to use
|
||||
implementation("com.google.firebase:firebase-analytics")
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "923472686173",
|
||||
"project_id": "doormilecustomer",
|
||||
"storage_bucket": "doormilecustomer.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:923472686173:android:175b38552da45f2d4ef1b4",
|
||||
"android_client_info": {
|
||||
"package_name": "com.doormile.customer"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAoxaF4fmYXU372qZkYAo7WftFqgiEg_7k"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
27
android/app/proguard-rules.pro
vendored
@@ -1,24 +1,3 @@
|
||||
# Flutter Wrapper
|
||||
-keep class io.flutter.app.** { *; }
|
||||
-keep class io.flutter.plugin.** { *; }
|
||||
-keep class io.flutter.util.** { *; }
|
||||
-keep class io.flutter.view.** { *; }
|
||||
-keep class io.flutter.** { *; }
|
||||
-keep class io.flutter.plugins.** { *; }
|
||||
|
||||
# Firebase
|
||||
-keep class com.google.firebase.** { *; }
|
||||
|
||||
# Flutter Deferred Components (Play Core)
|
||||
-dontwarn com.google.android.play.core.**
|
||||
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:\sdk\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
|
||||
|
||||
# Add any project specific keep options here:
|
||||
# Flutter's own rules are applied by the Flutter Gradle plugin. Keep this file
|
||||
# for app-specific rules — e.g. models used with reflection by a JSON codec.
|
||||
-keep class in.doormile.customer.** { *; }
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- DEBUG ONLY. Android 9+ blocks cleartext HTTP by default, which makes a
|
||||
local backend (http://<dev-machine-ip>:8080) unreachable from a real
|
||||
handset. This override lives in the debug manifest, so a release build
|
||||
is unaffected and still refuses cleartext.
|
||||
-->
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The app talks to the Doormile backend for serviceability, slots and
|
||||
bookings. Declared in the main manifest so release builds have it too. -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- Drops the pickup pin at the customer's door. Both are optional at
|
||||
runtime: refusing either leaves the map on the service-area centre and
|
||||
the address can still be searched. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
||||
<!-- The app is useful without GPS hardware, so Play should not filter it
|
||||
out for devices that lack it. -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:label="doormile"
|
||||
android:label="Doormile"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@@ -16,6 +32,7 @@
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
@@ -30,10 +47,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Google Maps API key — replace with your key from Google Cloud Console -->
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q" />
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
@@ -50,8 +63,5 @@
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.DIAL" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.doormile.customer
|
||||
package `in`.doormile.customer
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 69 B |
@@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Shown from process start until the first Flutter frame. Matches the white
|
||||
Login screen so the handover is invisible. -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<item android:drawable="@color/dm_surface" />
|
||||
<item
|
||||
android:gravity="center"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:drawable="@drawable/dm_mark_brand" />
|
||||
</layer-list>
|
||||
|
||||
|
Before Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 69 B |
24
android/app/src/main/res/drawable/dm_mark_brand.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Doormile mark in brand maroon, for the launch screen.
|
||||
Art is authored on a 24x24 grid and scaled into the 66dp safe zone. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="2.2" android:scaleY="2.2"
|
||||
android:translateX="27.6" android:translateY="27.6">
|
||||
<path
|
||||
android:pathData="M3.6,9.2 L12,4 L20.4,9.2 L20.4,20 L3.6,20 Z"
|
||||
android:strokeColor="#960019"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M9.6,20 L9.6,13.4 L14.4,13.4 L14.4,20"
|
||||
android:strokeColor="#960019"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
</vector>
|
||||
24
android/app/src/main/res/drawable/dm_mark_white.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Doormile mark in white, for the adaptive-icon foreground.
|
||||
Art is authored on a 24x24 grid and scaled into the 66dp safe zone. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="2.2" android:scaleY="2.2"
|
||||
android:translateX="27.6" android:translateY="27.6">
|
||||
<path
|
||||
android:pathData="M3.6,9.2 L12,4 L20.4,9.2 L20.4,20 L3.6,20 Z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M9.6,20 L9.6,13.4 L14.4,13.4 L14.4,20"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Shown from process start until the first Flutter frame. Matches the white
|
||||
Login screen so the handover is invisible. -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<item android:drawable="@color/dm_surface" />
|
||||
<item
|
||||
android:gravity="center"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:drawable="@drawable/dm_mark_brand" />
|
||||
</layer-list>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/dm_brand" />
|
||||
<foreground android:drawable="@drawable/dm_mark_white" />
|
||||
<monochrome android:drawable="@drawable/dm_mark_white" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/dm_brand" />
|
||||
<foreground android:drawable="@drawable/dm_mark_white" />
|
||||
<monochrome android:drawable="@drawable/dm_mark_white" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,21 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<!-- Android 12+ uses the platform splash screen API. -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowSplashScreenBackground">@color/dm_surface</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/dm_mark_brand</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">@color/dm_surface</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">@color/dm_surface</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<!-- Theme applied to the Android Window while the process starts. -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<!-- Theme applied once Flutter takes over drawing. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">@color/dm_surface</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<!-- Android 12+ uses the platform splash screen API. -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowSplashScreenBackground">@color/dm_surface</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/dm_mark_brand</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">@color/dm_surface</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:windowBackground">@color/dm_surface</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
6
android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Doormile brand tokens, mirrored from lib/ui/tokens.dart -->
|
||||
<color name="dm_brand">#960019</color>
|
||||
<color name="dm_surface">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,22 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<!-- Theme applied to the Android Window while the process starts. -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<!-- Theme applied once Flutter takes over drawing. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:windowBackground">@color/dm_surface</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
17
android/app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Nothing in this app should be backed up or transferred between devices:
|
||||
the session is re-established by signing in. -->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="root" />
|
||||
<exclude domain="file" />
|
||||
<exclude domain="database" />
|
||||
<exclude domain="sharedpref" />
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="root" />
|
||||
<exclude domain="file" />
|
||||
<exclude domain="database" />
|
||||
<exclude domain="sharedpref" />
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -1,10 +1,6 @@
|
||||
org.gradle.jvmargs=-Xmx2560m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8
|
||||
org.gradle.parallel=false
|
||||
org.gradle.workers.max=1
|
||||
org.gradle.daemon=false
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
# This newDsl flag was added by the Flutter template
|
||||
android.newDsl=false
|
||||
# This builtInKotlin flag was added by the Flutter template
|
||||
android.builtInKotlin=false
|
||||
kotlin.incremental=false
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
|
||||
|
||||
6
android/key.properties.example
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copy to android/key.properties and fill in before `flutter build appbundle`.
|
||||
# Never commit the real file or the .jks keystore.
|
||||
storePassword=your-keystore-password
|
||||
keyPassword=your-key-password
|
||||
keyAlias=upload
|
||||
storeFile=upload-keystore.jks
|
||||
@@ -19,9 +19,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "9.0.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
|
||||
id("com.google.gms.google-services") version "4.4.4" apply false
|
||||
id("com.android.application") version "9.1.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.4.0" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
93
assets/fonts/GeistMono-LICENSE.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font.git)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
assets/fonts/GeistMono-VF.ttf
Normal file
93
assets/fonts/Manrope-LICENSE.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2018 The Manrope Project Authors (https://github.com/sharanda/manrope)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
assets/fonts/Manrope-VF.ttf
Normal file
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 966 KiB |
|
Before Width: | Height: | Size: 1015 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1015 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 570 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |