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/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/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.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "924134a44c189315be2148659913dda1671cbe99"
|
revision: "d3b14c876900e553bc736ca19295fc09e3853e8e"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
@@ -13,20 +13,17 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
- platform: android
|
- platform: android
|
||||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
- platform: ios
|
- platform: ios
|
||||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
- platform: macos
|
|
||||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
|
||||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
|
||||||
- platform: web
|
- platform: web
|
||||||
create_revision: 924134a44c189315be2148659913dda1671cbe99
|
create_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
base_revision: 924134a44c189315be2148659913dda1671cbe99
|
base_revision: d3b14c876900e553bc736ca19295fc09e3853e8e
|
||||||
|
|
||||||
# User provided section
|
# 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.
|
A Flutter app for the Doormile customer journey:
|
||||||
EV-first delivery, MileTruth AI routing narrative, corporate-red design system.
|
|
||||||
|
|
||||||
## What's inside (all screens implemented)
|
**Book pickup → Track → Delivered**
|
||||||
|
|
||||||
| Flow | Screens |
|
State is a `ChangeNotifier` behind an `InheritedNotifier`; navigation is
|
||||||
|------|---------|
|
`Navigator` with named booking steps. Real maps come from `flutter_map` over
|
||||||
| Onboarding | Splash → 3-slide Onboarding |
|
OpenStreetMap/CARTO tiles and real positions from `geolocator` — no API key and
|
||||||
| Auth | Login (phone) → OTP verify |
|
no billing account required. Everything else is the Flutter SDK.
|
||||||
| 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
|
|
||||||
|
|
||||||
```
|
```
|
||||||
lib/
|
lib/
|
||||||
main.dart # app entry, theme, named routes
|
main.dart app entry, theme, page transitions
|
||||||
theme/ # colours + text styles (design tokens)
|
data/
|
||||||
models/ # Order, AddressEntry, WalletTransaction, ...
|
models.dart Booking, Destination, DeliveryDetails, JourneyStage…
|
||||||
state/app_state.dart # Provider store + mock data + booking draft
|
doormile_api.dart the service surface — one implementation
|
||||||
widgets/ # shared UI (AppCard, PrimaryButton, StatusChip, logo)
|
live_doormile_api.dart ← the ONLY place the app talks to a server
|
||||||
screens/ # one file per screen, grouped by flow
|
location_service.dart ← the ONLY place device GPS is touched
|
||||||
test/ # smoke, full-journey, and per-screen render tests
|
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
|
## Tests
|
||||||
|
|
||||||
```bash
|
The test suite is the only place a fake backend exists:
|
||||||
flutter test # 18 tests: boot, full user journey, every screen renders clean
|
`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.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- build/**
|
||||||
|
- android/**
|
||||||
|
- ios/**
|
||||||
|
- web/**
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import java.util.Properties
|
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 {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
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 {
|
android {
|
||||||
namespace = "com.doormile.customer"
|
namespace = "in.doormile.customer"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@@ -26,33 +29,46 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
applicationId = "in.doormile.customer"
|
||||||
applicationId = "com.doormile.customer"
|
minSdk = 24
|
||||||
// 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
|
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
create("release") {
|
if (hasUploadKey) {
|
||||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
create("release") {
|
||||||
keyPassword = keystoreProperties["keyPassword"] as String?
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||||
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||||
storePassword = keystoreProperties["storePassword"] as String?
|
storeFile = rootProject.file(keystoreProperties.getProperty("storeFile"))
|
||||||
|
storePassword = keystoreProperties.getProperty("storePassword")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig = if (keystorePropertiesFile.exists()) {
|
signingConfig = if (hasUploadKey) {
|
||||||
signingConfigs.getByName("release")
|
signingConfigs.getByName("release")
|
||||||
} else {
|
} else {
|
||||||
signingConfigs.getByName("debug")
|
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 {
|
flutter {
|
||||||
source = "../.."
|
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
|
# Flutter's own rules are applied by the Flutter Gradle plugin. Keep this file
|
||||||
-keep class io.flutter.app.** { *; }
|
# for app-specific rules — e.g. models used with reflection by a JSON codec.
|
||||||
-keep class io.flutter.plugin.** { *; }
|
-keep class in.doormile.customer.** { *; }
|
||||||
-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:
|
|
||||||
|
|||||||
@@ -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 INTERNET permission is required for development. Specifically,
|
||||||
the Flutter tool needs it to communicate with the running application
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<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>
|
</manifest>
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<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" />
|
<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_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_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
|
<application
|
||||||
android:label="doormile"
|
android:label="Doormile"
|
||||||
android:name="${applicationName}"
|
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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -16,6 +32,7 @@
|
|||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@@ -30,10 +47,6 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
@@ -50,8 +63,5 @@
|
|||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.DIAL" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.doormile.customer
|
package `in`.doormile.customer
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
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"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item android:drawable="@color/dm_surface" />
|
||||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
<item
|
||||||
</item>
|
android:gravity="center"
|
||||||
<item>
|
android:width="120dp"
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
android:height="120dp"
|
||||||
</item>
|
android:drawable="@drawable/dm_mark_brand" />
|
||||||
</layer-list>
|
</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"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item android:drawable="@color/dm_surface" />
|
||||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
<item
|
||||||
</item>
|
android:gravity="center"
|
||||||
<item>
|
android:width="120dp"
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
android:height="120dp"
|
||||||
</item>
|
android:drawable="@drawable/dm_mark_brand" />
|
||||||
</layer-list>
|
</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
<!-- Android 12+ uses the platform splash screen API. -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:forceDarkAllowed">false</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowSplashScreenBackground">@color/dm_surface</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/dm_mark_brand</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowSplashScreenIconBackgroundColor">@color/dm_surface</item>
|
||||||
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
This theme determines the color of the Android Window while your
|
<item name="android:windowBackground">@color/dm_surface</item>
|
||||||
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>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
<!-- Theme applied to the Android Window while the process starts. -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<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: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>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied once Flutter takes over drawing. -->
|
||||||
This theme determines the color of the Android Window while your
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
<item name="android:windowBackground">@color/dm_surface</item>
|
||||||
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>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:forceDarkAllowed">false</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowSplashScreenBackground">@color/dm_surface</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/dm_mark_brand</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowSplashScreenIconBackgroundColor">@color/dm_surface</item>
|
||||||
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
|
||||||
</style>
|
</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">
|
<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>
|
</style>
|
||||||
</resources>
|
</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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">
|
<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: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>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied once Flutter takes over drawing. -->
|
||||||
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">
|
<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>
|
</style>
|
||||||
</resources>
|
</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.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
org.gradle.parallel=false
|
|
||||||
org.gradle.workers.max=1
|
|
||||||
org.gradle.daemon=false
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# This newDsl flag was added by the Flutter template
|
# This newDsl flag was added by the Flutter template
|
||||||
android.newDsl=false
|
android.newDsl=false
|
||||||
# This builtInKotlin flag was added by the Flutter template
|
# This builtInKotlin flag was added by the Flutter template
|
||||||
android.builtInKotlin=false
|
android.builtInKotlin=false
|
||||||
kotlin.incremental=false
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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 {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "9.0.1" apply false
|
id("com.android.application") version "9.1.0" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
|
id("org.jetbrains.kotlin.android") version "2.4.0" apply false
|
||||||
id("com.google.gms.google-services") version "4.4.4" apply false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
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 |