Add all project files
|
|
@ -0,0 +1,45 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "02085feb3f5d8a8156e5e28512b9d99351d510c0"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
- platform: android
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
- platform: ios
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
- platform: linux
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
- platform: macos
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
- platform: web
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
- platform: windows
|
||||
create_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
base_revision: 02085feb3f5d8a8156e5e28512b9d99351d510c0
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
# Delivery App — Build & Feature Changelog
|
||||
**Date:** 2026-04-29
|
||||
**Developer:** Raphael (Emergency: kimi-k2.6)
|
||||
**Target:** Wesley vdk (Telegram)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completed Features
|
||||
|
||||
### 1. Map Themes (Fixed)
|
||||
- **Standard** — OpenStreetMap default tiles
|
||||
- **Dark** — CartoDB Dark Matter (replaced Stadia Dark which returned 401 Unauthorized)
|
||||
- **CartoDB Voyager** — Light themed, good contrast with app UI
|
||||
|
||||
*Note: Stadia Dark requires API key. CartoDB Dark Matter is free.*
|
||||
|
||||
### 2. Overpass Address Integration
|
||||
- Fetched **5,855 addresses** (151 streets) in Hoogerheide via Overpass API
|
||||
- Stored locally in `assets/hoogerheide_addresses.json`
|
||||
- App now checks local cache first (instant), falls back to Nominatim with timeout
|
||||
- Added "Refresh coordinates" button in RoutePage AppBar
|
||||
|
||||
### 3. Delete Functionality
|
||||
- **Delete stops:** Via stop details bottom sheet (red button)
|
||||
- **Delete routes:** Trash icon next to each route in RoutesPage
|
||||
- Both require confirmation dialog
|
||||
|
||||
### 4. Route Optimization & Display
|
||||
- **OSRM trip routing** for proper TSP (Traveling Salesman Problem) optimization
|
||||
- **Route polyline display** using `flutter_map` PolylineLayer
|
||||
- **Loading indicators** when calculating routes
|
||||
- Route button (↗️) toggles display on/off
|
||||
|
||||
### 5. Build Environment Setup
|
||||
- Installed **Eclipse Temurin JDK 17** (`/workspace/jdk/`)
|
||||
- Installed **Android SDK** (`/workspace/android-sdk/`)
|
||||
- Installed **CMake 3.22.1** via Android SDK manager
|
||||
- Fixed missing `source_span: ^1.10.0` dependency
|
||||
- APK output: **49MB** (consistent across builds)
|
||||
|
||||
---
|
||||
|
||||
## ❌ Known Issues / Unresolved
|
||||
|
||||
### 1. Route Line Not Displaying
|
||||
**Status:** Unresolved
|
||||
**Symptoms:**
|
||||
- Tapping route button shows loading indicator
|
||||
- After completion, no polyline appears on map
|
||||
- OSRM API returns valid geometry (tested manually)
|
||||
- Polyline decoding appears correct (debug prints added)
|
||||
|
||||
**Likely causes:**
|
||||
- PolylineLayer z-index issue (might be behind tiles)
|
||||
- `_showRoute` state not triggering rebuild
|
||||
- Decoded points might be malformed
|
||||
- OSRM returns encoded polyline, decoding might fail silently
|
||||
|
||||
**Debug steps:**
|
||||
```bash
|
||||
adb logcat | grep flutter # View debug prints from phone
|
||||
```
|
||||
|
||||
### 2. Stadia Dark Theme Unusable
|
||||
**Status:** Replaced with CartoDB Dark
|
||||
**Root cause:** `tiles.stadiamaps.com` returns 401 Unauthorized
|
||||
**Solution:** Use CartoDB Dark Matter (free, no auth) or get Stadia API key
|
||||
|
||||
### 3. Coordinate Accuracy
|
||||
**Status:** Improved, not perfect
|
||||
**Issue:** Some stops still map to incorrect locations
|
||||
**Cause:** Database has old/wrong coordinates from previous Nominatim failures
|
||||
**Fix:** Use "Refresh coordinates" button to re-geocode all stops using Overpass data
|
||||
|
||||
### 4. APK Caching on Telegram
|
||||
**Status:** Partially fixed
|
||||
**Issue:** Telegram caches APK files, installing old versions
|
||||
**Workaround:** Rename APK with timestamp before sending (e.g., `delivery-v20260429-2126-debug.apk`)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technical Debt
|
||||
|
||||
### Code Issues
|
||||
1. **Missing commas** — Several Dart function calls missing commas between arguments (fixed in multiple places)
|
||||
2. **Fallback logic** — If OSRM fails, falls back to straight lines between stops (not following streets)
|
||||
3. **No error UI** — Route/optimization failures fail silently (only debugPrint)
|
||||
4. **State management** — `_isLoadingRoute` and `_showRoute` might have race conditions
|
||||
|
||||
### Dependencies Added
|
||||
```yaml
|
||||
dependencies:
|
||||
source_span: ^1.10.0 # Fix for string_scanner incompatibility
|
||||
```
|
||||
|
||||
### Files Modified
|
||||
- `lib/main.dart` — Main app logic (routes, stops, map, geocoding)
|
||||
- `pubspec.yaml` — Added assets reference
|
||||
- `assets/hoogerheide_addresses.json` — 5,855 addresses from Overpass
|
||||
- `lib/hoogerheide_streets.dart` — Generated street coordinates (unused now)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Test Checklist
|
||||
|
||||
- [ ] Route line displays after tapping ↗️ button
|
||||
- [ ] Loading indicator shows during route calculation
|
||||
- [ ] Route optimization reorders stops correctly
|
||||
- [ ] Delete stop works with confirmation
|
||||
- [ ] Delete route works with confirmation
|
||||
- [ ] Dark theme (CartoDB) renders correctly
|
||||
- [ ] "Refresh coordinates" updates stop positions
|
||||
- [ ] Overpass data loads (no network needed after first fetch)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps (If Resuming)
|
||||
|
||||
1. **Fix route display:**
|
||||
- Check `adb logcat` for debug prints
|
||||
- Verify PolylineLayer renders (try hardcoded points)
|
||||
- Test with 2-3 stops only
|
||||
|
||||
2. **Improve error handling:**
|
||||
- Show SnackBar on route/optimization failure
|
||||
- Add retry button
|
||||
|
||||
3. **Local Nominatim (optional):**
|
||||
- Install PostgreSQL + Nominatim with Netherlands data
|
||||
- Solves rate-limiting issues permanently
|
||||
|
||||
4. **Unit tests:**
|
||||
- Test polyline decoding
|
||||
- Test geocoding logic
|
||||
- Test route optimization
|
||||
|
||||
---
|
||||
|
||||
## 📦 Build Commands
|
||||
|
||||
```bash
|
||||
# Clean build
|
||||
flutter clean && flutter pub get
|
||||
|
||||
# Release APK
|
||||
export JAVA_HOME="/home/node/.openclaw/workspace/jdk/jdk-17.0.9+9"
|
||||
export ANDROID_HOME="/home/node/.openclaw/workspace/android-sdk"
|
||||
export PATH="$JAVA_HOME/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:/home/node/.openclaw/workspace/flutter/bin:$PATH"
|
||||
flutter build apk --release
|
||||
|
||||
# Output: build/app/outputs/flutter-apk/app-release.apk (49MB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last updated:** 2026-04-29 23:15 UTC
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
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`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.delivery_app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.delivery_app"
|
||||
// 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
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 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"/>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<application
|
||||
android:label="delivery_app"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
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
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.delivery_app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<?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 -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</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>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?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 -->
|
||||
<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>
|
||||
</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>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 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"/>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// Fetch all addresses in Hoogerheide using Overpass API
|
||||
// Run with: dart fetch_addresses_overpass.dart
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
const hoogerheideRelationId = 2716716;
|
||||
const overpassUrl = 'https://overpass-api.de/api/interpreter';
|
||||
|
||||
// Overpass query to get all addresses (nodes with addr:housenumber) in Hoogerheide
|
||||
const overpassQuery = '''
|
||||
[out:json][timeout:60];
|
||||
relation($hoogerheideRelationId);
|
||||
map_to_area -> .searchArea;
|
||||
(
|
||||
node["addr:housenumber"]["addr:street"](area.searchArea);
|
||||
way["addr:housenumber"]["addr:street"](area.searchArea);
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
''';
|
||||
|
||||
Future<void> main() async {
|
||||
print('Fetching addresses from Hoogerheide (OSM relation $hoogerheideRelationId)...');
|
||||
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse(overpassUrl),
|
||||
body: {'data': overpassQuery},
|
||||
headers: {'User-Agent': 'DeliveryApp/1.0'},
|
||||
).timeout(const Duration(seconds: 60));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
print('Error: HTTP ${response.statusCode}');
|
||||
print(response.body);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
final data = jsonDecode(response.body);
|
||||
final elements = data['elements'] as List;
|
||||
|
||||
// Parse addresses
|
||||
final addresses = <Map<String, dynamic>>[];
|
||||
final streetCoords = <String, List<double>>{};
|
||||
|
||||
for (final el in elements) {
|
||||
if (el['type'] == 'node') {
|
||||
final tags = el['tags'] ?? {};
|
||||
final street = tags['addr:street'] ?? tags['name'];
|
||||
final housenumber = tags['addr:housenumber'];
|
||||
final lat = el['lat'];
|
||||
final lon = el['lon'];
|
||||
|
||||
if (street != null && housenumber != null && lat != null && lon != null) {
|
||||
addresses.add({
|
||||
'street': street,
|
||||
'housenumber': housenumber,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
});
|
||||
|
||||
// Store first coord for each street
|
||||
if (!streetCoords.containsKey(street)) {
|
||||
streetCoords[street] = [lat, lon];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print('Found ${addresses.length} addresses on ${streetCoords.length} streets.');
|
||||
|
||||
// Save to JSON
|
||||
final output = {
|
||||
'addresses': addresses,
|
||||
'streetCoords': streetCoords.map((k, v) => MapEntry(k, {'lat': v[0], 'lon': v[1]})),
|
||||
'fetched_at': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
final file = File('assets/hoogerheide_addresses.json');
|
||||
await file.create(recursive: true);
|
||||
await file.writeAsString(jsonEncode(output));
|
||||
print('Saved to ${file.path}');
|
||||
|
||||
// Also create a Dart file with streetCoords for easy import
|
||||
final dartContent = StringBuffer();
|
||||
dartContent.writeln('// Auto-generated from Overpass API');
|
||||
dartContent.writeln('// Hoogerheide addresses fetched from OSM');
|
||||
dartContent.writeln();
|
||||
dartContent.writeln('class HoogerheideStreets {');
|
||||
dartContent.writeln(' static const streetCoords = {');
|
||||
|
||||
streetCoords.forEach((street, coords) {
|
||||
dartContent.writeln(" '$street': [${coords[0]}, ${coords[1]}],");
|
||||
});
|
||||
|
||||
dartContent.writeln(' };');
|
||||
dartContent.writeln('}');
|
||||
|
||||
final dartFile = File('lib/hoogerheide_streets.dart');
|
||||
await dartFile.writeAsString(dartContent.toString());
|
||||
print('Saved Dart file to ${dartFile.path}');
|
||||
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "Generated.xcconfig"
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "Generated.xcconfig"
|
||||
|
|
@ -0,0 +1,620 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
|
@ -0,0 +1,5 @@
|
|||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Delivery App</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>delivery_app</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1 @@
|
|||
#import "GeneratedPluginRegistrant.h"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: FlutterSceneDelegate {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
// Auto-generated from Overpass API
|
||||
// Hoogerheide addresses fetched from OSM
|
||||
|
||||
class HoogerheideStreets {
|
||||
static const streetCoords = {
|
||||
'KLM laan': [51.426372, 4.3287999],
|
||||
'Antwerpsestraatweg': [51.455173, 4.311831],
|
||||
'Kooiweg': [51.4301532, 4.3482979],
|
||||
'Huijbergseweg': [51.4232902, 4.3255204],
|
||||
'Groene Papegaai': [51.4167497, 4.3489786],
|
||||
'Voltweg': [51.4277433, 4.3528528],
|
||||
'Middenweg': [51.4280731, 4.3470335],
|
||||
'Buitendreef': [51.4257565, 4.3477789],
|
||||
'Edisonweg': [51.4258101, 4.346728],
|
||||
'Postweg': [51.4263358, 4.343193],
|
||||
'Wattweg': [51.427048, 4.3514838],
|
||||
'Ampèreweg': [51.4279279, 4.349355],
|
||||
'Scheidreef': [51.4159562, 4.3444209],
|
||||
'Abdijlaan': [51.4229797, 4.3641471],
|
||||
'Westerstraat': [51.4274419, 4.360346],
|
||||
'Cleirenbaantje': [51.4387315, 4.3177285],
|
||||
'Lange Steen': [51.4585387, 4.3206835],
|
||||
'Oude Steenstraat': [51.4389962, 4.3158047],
|
||||
'Aviolandalaan': [51.4379361, 4.3363898],
|
||||
'Oude Stee': [51.4375433, 4.3279924],
|
||||
'Zuidgeest': [51.4606941, 4.3263748],
|
||||
'Groeneweg': [51.4449494, 4.3170313],
|
||||
'De Zonnebloem': [51.4299878, 4.3141787],
|
||||
'dokter van de Karplein': [51.4305535, 4.3225466],
|
||||
"in d' Hoef": [51.4294856, 4.3219868],
|
||||
'op den Duyn': [51.4303211, 4.3205981],
|
||||
'van Houtenstraat': [51.4201577, 4.3250032],
|
||||
'Van Weerden Poelmanlaan': [51.4259232, 4.3285434],
|
||||
'van der Dilftstraat': [51.4218531, 4.3289069],
|
||||
'Van der Meulenplein': [51.421976, 4.3242974],
|
||||
'Wouwbaan': [51.4335841, 4.3335207],
|
||||
'Bijentiende': [51.4305551, 4.3266769],
|
||||
'Binnenweg': [51.4260382, 4.324176],
|
||||
'Blériotlaan': [51.4272992, 4.3345036],
|
||||
'Burg Moorsstraat': [51.4214962, 4.3298462],
|
||||
'Canadalaan': [51.4261836, 4.3145241],
|
||||
'Doelstraat': [51.4333178, 4.3122133],
|
||||
'Duinhoefplein': [51.4294, 4.324102],
|
||||
'Edward Jennerstraat': [51.4282344, 4.3244843],
|
||||
'Fokkerlaan': [51.4282773, 4.3301922],
|
||||
'Gemeynte': [51.4304574, 4.3289701],
|
||||
'Geyssendorfferlaan': [51.4266993, 4.333038],
|
||||
'Gravesandestraat': [51.4248846, 4.3330656],
|
||||
'Heideduinstede': [51.42866, 4.3270955],
|
||||
'Hugo de Grootstraat': [51.4255321, 4.3369589],
|
||||
'Huijgensstraat': [51.4267075, 4.3355346],
|
||||
'Kamerlingh Onnesstraat': [51.4252366, 4.3344166],
|
||||
'Kloosterstraat': [51.4252023, 4.3261654],
|
||||
'Lammertiende': [51.4299257, 4.3260257],
|
||||
'Lindberghlaan': [51.4257616, 4.3315703],
|
||||
'Maststraat': [51.420274, 4.3308809],
|
||||
'Meulenblock': [51.4225239, 4.3264912],
|
||||
'Mgr Ariensstraat': [51.4220711, 4.3268278],
|
||||
'Mgr Frenckenstraat': [51.4213404, 4.3275349],
|
||||
'Mgr Nolensstraat': [51.4192619, 4.3253404],
|
||||
'Mgr Poelsstraat': [51.419882, 4.3243339],
|
||||
'Minckelersweg': [51.4253221, 4.3314398],
|
||||
'Molenstraat': [51.4217376, 4.3287896],
|
||||
'Nieuweweg': [51.4283841, 4.317262],
|
||||
'Norbartstraat': [51.4175717, 4.3223607],
|
||||
'Oostlaan': [51.427153, 4.3357852],
|
||||
'Ossendrechtseweg': [51.4212836, 4.3229844],
|
||||
'Ouwe Raedthuysplein': [51.4254982, 4.3223923],
|
||||
'Parmentierlaan': [51.4278312, 4.3295698],
|
||||
'Past van Roesselstraat': [51.4219015, 4.3314917],
|
||||
'Pasteurstraat': [51.4276915, 4.321463],
|
||||
'Plesmanlaan': [51.4272202, 4.3295984],
|
||||
'Pr Bernhardstraat': [51.426686, 4.3242328],
|
||||
'Pr Hendrikstraat': [51.4249937, 4.3289311],
|
||||
'Putseweg': [51.4214453, 4.3262241],
|
||||
'Raadhuisstraat': [51.4276685, 4.3205862],
|
||||
'Rubertstraat': [51.4199567, 4.3305864],
|
||||
'Schapendreef': [51.4185549, 4.3266796],
|
||||
'Smirnofflaan': [51.427333, 4.32915],
|
||||
'Sondermanlaan': [51.4265502, 4.332625],
|
||||
'St Lucasplein': [51.4281559, 4.3227749],
|
||||
'Suijkerbuijkstraat': [51.4220294, 4.3310098],
|
||||
'Trefpunt': [51.4223518, 4.3276936],
|
||||
'De Acacia': [51.4315721, 4.3124204],
|
||||
'De Anjer': [51.4329841, 4.3177062],
|
||||
'Nijverheidstraat': [51.432094, 4.3294208],
|
||||
'Onderstal': [51.4247833, 4.3207034],
|
||||
'Paus Leo XIIIestraat': [51.4213374, 4.3281964],
|
||||
'Robert Kochstraat': [51.4270534, 4.3245148],
|
||||
'Semmelweissstraat': [51.427228, 4.3245222],
|
||||
'Struikenlaan': [51.432396, 4.3134064],
|
||||
'Torontolaan': [51.427983, 4.3156753],
|
||||
'Aalbersestraat': [51.4216353, 4.3237076],
|
||||
'Bloemenlaan': [51.4339682, 4.3164694],
|
||||
'Flemingstraat': [51.4269028, 4.3223219],
|
||||
'De Hazelaar': [51.4320928, 4.3160198],
|
||||
'Heistraat': [51.428411, 4.3264409],
|
||||
'Buys Ballotstraat': [51.4240891, 4.3332657],
|
||||
'Cameronlaan': [51.4275219, 4.3173398],
|
||||
'Couwenberghstraat': [51.4188591, 4.3324664],
|
||||
'Doctor de Bruijnlaan': [51.41957, 4.3315586],
|
||||
'Duinstraat': [51.4288671, 4.3228877],
|
||||
'Hamiltonlaan': [51.4269186, 4.3153105],
|
||||
'Jef Adriaansenstraat': [51.4209707, 4.3284062],
|
||||
'Keesomstraat': [51.4241965, 4.3322135],
|
||||
'Laan Olieslagers': [51.4271304, 4.3315992],
|
||||
'Philomenahof': [51.423116, 4.3245192],
|
||||
'Plantagelaan': [51.4183436, 4.3341173],
|
||||
'Pr Clausstraat': [51.4243724, 4.3298082],
|
||||
'Sportlaan': [51.4184943, 4.3396104],
|
||||
'Wipstraat': [51.4340589, 4.3263907],
|
||||
'Zandfort': [51.4347749, 4.3201322],
|
||||
'De Berk': [51.4312753, 4.3149364],
|
||||
'De Eik': [51.4304264, 4.3125547],
|
||||
'De Papaver': [51.4314924, 4.3163849],
|
||||
'De Roos': [51.4309908, 4.3172296],
|
||||
'De Sering': [51.4302519, 4.3163224],
|
||||
'De Tulp': [51.4297856, 4.3157344],
|
||||
'De Vuurdoorn': [51.4324287, 4.3123062],
|
||||
'De Wilg': [51.4292073, 4.3139065],
|
||||
'Van Ostaayland': [51.43078, 4.3270783],
|
||||
'Van de Moerstraat': [51.4197106, 4.3321879],
|
||||
'Duintjesplein': [51.4214555, 4.3283374],
|
||||
'Garry Horselaan': [51.4273601, 4.3134323],
|
||||
'Griblingstraat': [51.428407, 4.3234432],
|
||||
'Kromstraat': [51.4248337, 4.3251587],
|
||||
'Lorentzstraat': [51.4247752, 4.3315962],
|
||||
'Matthias Wolffstraat': [51.4245236, 4.3276479],
|
||||
'Olympialaan': [51.4175887, 4.3341347],
|
||||
'Platengastraat': [51.4284275, 4.3247368],
|
||||
'Valkestraat': [51.4341428, 4.3139484],
|
||||
'Verlengde Duinstraat': [51.4292189, 4.3299403],
|
||||
'Vogelven': [51.4313195, 4.3239172],
|
||||
'De Hortensia': [51.4328966, 4.3152865],
|
||||
'De Jasmijn': [51.4324091, 4.3146387],
|
||||
'De Narcis': [51.4320948, 4.3170455],
|
||||
'de Wildert': [51.430832, 4.3254337],
|
||||
"van 't Hoffstraat": [51.4240584, 4.3308651],
|
||||
'Steenstraat': [51.4385336, 4.3133992],
|
||||
'Dennenlaan': [51.4177953, 4.3301476],
|
||||
'Jan van der Heijdenstraat': [51.425478, 4.3209106],
|
||||
'Nieuwe Stee': [51.4359528, 4.3250954],
|
||||
'Rozenlaan': [51.4305681, 4.316808],
|
||||
'Lindonk': [51.4493934, 4.3067774],
|
||||
'Vossenweg': [51.4555639, 4.3022206],
|
||||
'Beukendreef': [51.4541488, 4.2999331],
|
||||
'Spoorbaan': [51.4452931, 4.2959054],
|
||||
'Hof van Holland': [51.4240189, 4.3231046],
|
||||
"op d'Hei": [51.424878, 4.3279444],
|
||||
'de Groenling': [51.4315484, 4.3203557],
|
||||
'Jac Jansenweg': [51.4252026, 4.3415318],
|
||||
'Arendsberg': [51.4266677, 4.3204963],
|
||||
'Haviksberg': [51.4264578, 4.3204574],
|
||||
'Uilenberg': [51.4251903, 4.3195762],
|
||||
'Oude Hof': [51.4201568, 4.3212737],
|
||||
'Hof van Brabant': [51.4293219, 4.3316424],
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'providers/app_state.dart';
|
||||
import 'pages/routes_page.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => AppState()..loadSettings(),
|
||||
child: const DeliveryApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class DeliveryApp extends StatelessWidget {
|
||||
const DeliveryApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
|
||||
return MaterialApp(
|
||||
title: 'Delivery Route',
|
||||
theme: appState.theme,
|
||||
home: const RoutesPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
class DeliveryRecord {
|
||||
final int? id;
|
||||
final int routeId;
|
||||
final int stopId;
|
||||
final String date; // YYYY-MM-DD
|
||||
final DateTime deliveredAt;
|
||||
|
||||
DeliveryRecord({
|
||||
this.id,
|
||||
required this.routeId,
|
||||
required this.stopId,
|
||||
required this.date,
|
||||
required this.deliveredAt,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
if (id != null) 'id': id,
|
||||
'route_id': routeId,
|
||||
'stop_id': stopId,
|
||||
'date': date,
|
||||
'delivered_at': deliveredAt.toIso8601String(),
|
||||
};
|
||||
|
||||
factory DeliveryRecord.fromMap(Map<String, dynamic> map) => DeliveryRecord(
|
||||
id: map['id'] as int?,
|
||||
routeId: map['route_id'] as int,
|
||||
stopId: map['stop_id'] as int,
|
||||
date: map['date'] as String,
|
||||
deliveredAt: DateTime.parse(map['delivered_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
class DailyStats {
|
||||
final String date;
|
||||
final int deliveredCount;
|
||||
final int totalCount;
|
||||
|
||||
DailyStats({
|
||||
required this.date,
|
||||
required this.deliveredCount,
|
||||
required this.totalCount,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class Stop {
|
||||
final int? id;
|
||||
final String street;
|
||||
final String houseNumber;
|
||||
final String notes;
|
||||
final List<String> newspapers;
|
||||
final double lat;
|
||||
final double lng;
|
||||
final bool delivered;
|
||||
final int sequence;
|
||||
|
||||
Stop({
|
||||
this.id,
|
||||
required this.street,
|
||||
required this.houseNumber,
|
||||
this.newspapers = const [],
|
||||
required this.lat,
|
||||
required this.lng,
|
||||
this.delivered = false,
|
||||
this.notes = '',
|
||||
this.sequence = 0,
|
||||
});
|
||||
|
||||
LatLng get location => LatLng(lat, lng);
|
||||
|
||||
Stop copyWith({
|
||||
int? id,
|
||||
String? street,
|
||||
String? houseNumber,
|
||||
List<String>? newspapers,
|
||||
double? lat,
|
||||
double? lng,
|
||||
bool? delivered,
|
||||
String? notes,
|
||||
int? sequence,
|
||||
}) {
|
||||
return Stop(
|
||||
id: id ?? this.id,
|
||||
street: street ?? this.street,
|
||||
houseNumber: houseNumber ?? this.houseNumber,
|
||||
newspapers: newspapers ?? this.newspapers,
|
||||
lat: lat ?? this.lat,
|
||||
lng: lng ?? this.lng,
|
||||
delivered: delivered ?? this.delivered,
|
||||
notes: notes ?? this.notes,
|
||||
sequence: sequence ?? this.sequence,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
if (id != null) 'id': id,
|
||||
'street': street,
|
||||
'house_number': houseNumber,
|
||||
'newspapers': newspapers.join(','),
|
||||
'lat': lat,
|
||||
'lng': lng,
|
||||
'delivered': delivered ? 1 : 0,
|
||||
'notes': notes,
|
||||
'sequence': sequence,
|
||||
};
|
||||
}
|
||||
|
||||
factory Stop.fromMap(Map<String, dynamic> map) {
|
||||
return Stop(
|
||||
id: map['id'] as int?,
|
||||
street: map['street'] as String,
|
||||
houseNumber: map['house_number'] as String,
|
||||
newspapers: (map['newspapers'] as String?)?.split(',').where((x) => x.isNotEmpty).toList() ?? [],
|
||||
lat: map['lat'] as double,
|
||||
lng: map['lng'] as double,
|
||||
delivered: map['delivered'] == 1,
|
||||
notes: (map['notes'] as String?) ?? '',
|
||||
sequence: map['sequence'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$street $houseNumber (${newspapers.join(",")})';
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../models/stop.dart';
|
||||
import '../services/database.dart';
|
||||
import '../services/geocoding.dart';
|
||||
import '../hoogerheide_streets.dart';
|
||||
|
||||
class BuilderPage extends StatefulWidget {
|
||||
final int routeId;
|
||||
final VoidCallback onSave;
|
||||
const BuilderPage({super.key, required this.routeId, required this.onSave});
|
||||
@override
|
||||
State<BuilderPage> createState() => _BuilderPageState();
|
||||
}
|
||||
|
||||
class _BuilderPageState extends State<BuilderPage> {
|
||||
final _db = DatabaseService();
|
||||
final _geocoding = GeocodingService();
|
||||
final _streetCtrl = TextEditingController();
|
||||
final _numCtrl = TextEditingController();
|
||||
|
||||
final List<_StreetEntry> _entries = [];
|
||||
final List<String> _currentNums = [];
|
||||
final Map<String, Set<String>> _currentPapers = {};
|
||||
|
||||
bool _saving = false;
|
||||
|
||||
static const _availablePapers = ['BN', 'AD', 'TEL', 'VK'];
|
||||
static const _npColors = {
|
||||
'BN': Colors.red,
|
||||
'AD': Colors.blue,
|
||||
'TEL': Colors.orange,
|
||||
'VK': Colors.purple,
|
||||
};
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_streetCtrl.dispose();
|
||||
_numCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _addHouseNumber() {
|
||||
final n = _numCtrl.text.trim();
|
||||
if (n.isNotEmpty && !_currentNums.contains(n)) {
|
||||
setState(() {
|
||||
_currentNums.add(n);
|
||||
_currentPapers[n] = {'BN'}; // default newspaper
|
||||
});
|
||||
}
|
||||
_numCtrl.clear();
|
||||
}
|
||||
|
||||
void _togglePaper(String num, String paper) {
|
||||
setState(() {
|
||||
final current = _currentPapers[num] ?? {'BN'};
|
||||
if (current.contains(paper)) {
|
||||
current.remove(paper);
|
||||
} else {
|
||||
current.add(paper);
|
||||
}
|
||||
_currentPapers[num] = current;
|
||||
});
|
||||
}
|
||||
|
||||
void _saveStreet() {
|
||||
if (_currentNums.isEmpty || _streetCtrl.text.trim().isEmpty) return;
|
||||
setState(() {
|
||||
_entries.add(_StreetEntry(
|
||||
street: _streetCtrl.text.trim(),
|
||||
numPapers: Map.from(_currentPapers),
|
||||
));
|
||||
_currentNums.clear();
|
||||
_currentPapers.clear();
|
||||
_streetCtrl.clear();
|
||||
});
|
||||
}
|
||||
|
||||
void _removeEntry(_StreetEntry entry) {
|
||||
setState(() => _entries.remove(entry));
|
||||
}
|
||||
|
||||
Future<void> _saveAll() async {
|
||||
if (_entries.isEmpty) return;
|
||||
setState(() => _saving = true);
|
||||
|
||||
try {
|
||||
var seq = (await _db.getMaxSequence(widget.routeId) ?? -1) + 1;
|
||||
|
||||
for (final entry in _entries) {
|
||||
for (final npEntry in entry.numPapers.entries) {
|
||||
final houseNum = npEntry.key;
|
||||
final papers = npEntry.value.isEmpty ? {'BN'} : npEntry.value;
|
||||
|
||||
// Geocode: try GeocodingService first, fallback to hoogerheide_streets.dart
|
||||
LatLng coords;
|
||||
try {
|
||||
coords = await _geocoding.geocode(entry.street, houseNum);
|
||||
} catch (_) {
|
||||
coords = _geocodeFromStreets(entry.street, houseNum);
|
||||
}
|
||||
|
||||
await _db.insertStop(
|
||||
Stop(
|
||||
street: entry.street,
|
||||
houseNumber: houseNum,
|
||||
newspapers: papers.toList(),
|
||||
lat: coords.latitude,
|
||||
lng: coords.longitude,
|
||||
sequence: seq++,
|
||||
),
|
||||
widget.routeId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
widget.onSave();
|
||||
if (mounted) Navigator.pop(context);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Error saving: $e'), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallback geocode using hoogerheide_streets.dart with house-number offset
|
||||
LatLng _geocodeFromStreets(String street, String houseNum) {
|
||||
final ns = _normalize(street);
|
||||
|
||||
// Exact match
|
||||
for (final entry in HoogerheideStreets.streetCoords.entries) {
|
||||
if (_normalize(entry.key) == ns) {
|
||||
return _offsetByHouseNumber(entry.value[0], entry.value[1], houseNum);
|
||||
}
|
||||
}
|
||||
// Partial match
|
||||
for (final entry in HoogerheideStreets.streetCoords.entries) {
|
||||
final ek = _normalize(entry.key);
|
||||
if (ns.contains(ek) || ek.contains(ns)) {
|
||||
return _offsetByHouseNumber(entry.value[0], entry.value[1], houseNum);
|
||||
}
|
||||
}
|
||||
return const LatLng(51.4243390, 4.3238380);
|
||||
}
|
||||
|
||||
LatLng _offsetByHouseNumber(double baseLat, double baseLng, String houseNum) {
|
||||
final hn = int.tryParse(houseNum.replaceAll(RegExp(r'[A-Za-z]'), '')) ?? 1;
|
||||
return LatLng(
|
||||
baseLat + (hn - 1) * 0.00015,
|
||||
baseLng + ((hn / 25).floor() % 2 == 0 ? 0 : 0.0002),
|
||||
);
|
||||
}
|
||||
|
||||
String _normalize(String name) =>
|
||||
name.replaceAll(RegExp(r'[–—]'), ' ').replaceAll(RegExp(r'\s+'), ' ').trim().toLowerCase();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Add Stops'),
|
||||
actions: [
|
||||
if (_saving)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: 20, height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
||||
),
|
||||
)
|
||||
else
|
||||
IconButton(
|
||||
icon: const Icon(Icons.check),
|
||||
onPressed: _entries.isEmpty ? null : _saveAll,
|
||||
tooltip: 'Save all stops',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Input card
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _streetCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Street name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _numCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'House number',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: (_) => _addHouseNumber(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton.filled(
|
||||
onPressed: _addHouseNumber,
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_currentNums.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: _currentNums.map(_buildHouseNumberChip).toList(),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _currentNums.isEmpty ? null : _saveStreet,
|
||||
icon: const Icon(Icons.add_road),
|
||||
label: const Text('Add Street'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Saved entries
|
||||
if (_entries.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Text('Stops to add:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
const SizedBox(height: 8),
|
||||
..._entries.map((entry) => Card(
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(child: Icon(Icons.location_on)),
|
||||
title: Text(entry.street),
|
||||
subtitle: Text(
|
||||
entry.numPapers.entries.map((e) => '${e.key}: ${e.value.join(",")}').join('; '),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||
onPressed: () => _removeEntry(entry),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHouseNumberChip(String num) {
|
||||
final current = _currentPapers[num] ?? {'BN'};
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(num, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(width: 4),
|
||||
GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
_currentNums.remove(num);
|
||||
_currentPapers.remove(num);
|
||||
}),
|
||||
child: const Icon(Icons.close, size: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
children: _availablePapers.map((np) => FilterChip(
|
||||
label: Text(np, style: const TextStyle(fontSize: 11)),
|
||||
selected: current.contains(np),
|
||||
selectedColor: _npColors[np],
|
||||
onSelected: (_) => _togglePaper(num, np),
|
||||
visualDensity: VisualDensity.compact,
|
||||
padding: EdgeInsets.zero,
|
||||
)).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StreetEntry {
|
||||
final String street;
|
||||
final Map<String, Set<String>> numPapers;
|
||||
_StreetEntry({required this.street, required this.numPapers});
|
||||
}
|
||||
|
|
@ -0,0 +1,895 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import '../models/stop.dart';
|
||||
import '../providers/app_state.dart';
|
||||
import '../providers/delivery_provider.dart';
|
||||
import '../services/geocoding.dart';
|
||||
import '../services/routing.dart';
|
||||
import '../services/gpx_export.dart';
|
||||
import 'builder_page.dart';
|
||||
import 'statistics_page.dart';
|
||||
import 'settings_page.dart';
|
||||
|
||||
class RoutePage extends StatefulWidget {
|
||||
final int routeId;
|
||||
final String name;
|
||||
const RoutePage({super.key, required this.routeId, required this.name});
|
||||
@override
|
||||
State<RoutePage> createState() => _RoutePageState();
|
||||
}
|
||||
|
||||
class _RoutePageState extends State<RoutePage> {
|
||||
final _geocoding = GeocodingService();
|
||||
final _routing = RoutingService();
|
||||
final _mapController = MapController();
|
||||
final _searchController = TextEditingController();
|
||||
|
||||
Position? _userPosition;
|
||||
List<LatLng> _routePoints = [];
|
||||
bool _showRoute = false;
|
||||
bool _isLoadingRoute = false;
|
||||
double _routeDistanceKm = 0.0;
|
||||
String _searchQuery = '';
|
||||
late DeliveryProvider _deliveryProvider;
|
||||
|
||||
static const _npColors = {
|
||||
'BN': Colors.red,
|
||||
'AD': Colors.blue,
|
||||
'TEL': Colors.orange,
|
||||
'VK': Colors.purple,
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_deliveryProvider = DeliveryProvider();
|
||||
_deliveryProvider.loadStops(widget.routeId);
|
||||
_deliveryProvider.addListener(_onDeliveryChanged);
|
||||
_initGps();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_deliveryProvider.removeListener(_onDeliveryChanged);
|
||||
_deliveryProvider.dispose();
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onDeliveryChanged() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _initGps() async {
|
||||
try {
|
||||
var perm = await Geolocator.checkPermission();
|
||||
if (perm == LocationPermission.denied) {
|
||||
perm = await Geolocator.requestPermission();
|
||||
}
|
||||
if (perm == LocationPermission.denied || perm == LocationPermission.deniedForever) return;
|
||||
|
||||
Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(accuracy: LocationAccuracy.high),
|
||||
).listen((p) {
|
||||
if (mounted) setState(() => _userPosition = p);
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// ── Route Fetching ──────────────────────────────────────
|
||||
|
||||
Future<void> _fetchRoute() async {
|
||||
final stops = _deliveryProvider.stops;
|
||||
if (stops.length < 2) return;
|
||||
setState(() => _isLoadingRoute = true);
|
||||
|
||||
// Fetch both route polyline and distance
|
||||
final result = await _fetchRouteWithDistance(stops);
|
||||
|
||||
setState(() {
|
||||
if (result != null) {
|
||||
_routePoints = result.$1;
|
||||
_routeDistanceKm = result.$2;
|
||||
} else {
|
||||
_routePoints = stops.map((s) => s.location).toList();
|
||||
_routeDistanceKm = 0.0;
|
||||
}
|
||||
_showRoute = true;
|
||||
_isLoadingRoute = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<(List<LatLng>, double)?> _fetchRouteWithDistance(List<Stop> stops) async {
|
||||
if (stops.length < 2) return null;
|
||||
|
||||
final coords = stops.map((s) => '${s.lng},${s.lat}').join(';');
|
||||
final url =
|
||||
'https://router.project-osrm.org/route/v1/driving/$coords?overview=full&geometries=polyline';
|
||||
|
||||
try {
|
||||
final resp = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10));
|
||||
if (resp.statusCode == 200) {
|
||||
final data = jsonDecode(resp.body);
|
||||
if (data['code'] == 'Ok') {
|
||||
final routes = data['routes'] as List;
|
||||
if (routes.isNotEmpty) {
|
||||
final geometry = routes[0]['geometry'] as String;
|
||||
final distanceMeters = (routes[0]['distance'] as num).toDouble();
|
||||
final points = RoutingService.decodePolyline(geometry);
|
||||
return (points, distanceMeters / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Optimization ────────────────────────────────────────
|
||||
|
||||
Future<void> _optimizeStops() async {
|
||||
final stops = _deliveryProvider.stops;
|
||||
if (stops.length < 2) return;
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Optimizing route...'), duration: Duration(seconds: 3)),
|
||||
);
|
||||
}
|
||||
|
||||
final optimized = await _routing.optimizeRoute(stops);
|
||||
final newStops = optimized ?? RoutingService.optimizeLocally(stops);
|
||||
await _deliveryProvider.reorderStops(newStops);
|
||||
}
|
||||
|
||||
// ── Coordinate Refresh ──────────────────────────────────
|
||||
|
||||
Future<void> _refreshCoordinates() async {
|
||||
final stops = _deliveryProvider.stops;
|
||||
if (stops.isEmpty) return;
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Refreshing coordinates...'), duration: Duration(seconds: 2)),
|
||||
);
|
||||
}
|
||||
|
||||
for (final stop in stops) {
|
||||
try {
|
||||
final newPos = await _geocoding.geocode(stop.street, stop.houseNumber);
|
||||
if ((newPos.latitude - stop.lat).abs() > 0.0001 ||
|
||||
(newPos.longitude - stop.lng).abs() > 0.0001) {
|
||||
await _deliveryProvider.updateCoords(stop.id!, newPos.latitude, newPos.longitude);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Coordinates refreshed'), duration: Duration(seconds: 2)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Export GPX ──────────────────────────────────────────
|
||||
|
||||
Future<void> _exportGpx() async {
|
||||
final stops = _deliveryProvider.stops;
|
||||
if (stops.isEmpty) return;
|
||||
|
||||
try {
|
||||
final path = await GpxExportService.exportRoute(
|
||||
routeName: widget.name,
|
||||
stops: stops,
|
||||
trackPoints: _showRoute ? _routePoints : null,
|
||||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('GPX exported: $path'), duration: const Duration(seconds: 3)),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Export failed: $e'), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Reset Today ─────────────────────────────────────────
|
||||
|
||||
Future<void> _resetToday() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Reset Today'),
|
||||
content: const Text(
|
||||
'This will mark all stops as undelivered for today. '
|
||||
'Previous delivery records are preserved in history. Continue?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.orange),
|
||||
child: const Text('Reset'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
await _deliveryProvider.resetToday();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Today\'s deliveries have been reset')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Search ──────────────────────────────────────────────
|
||||
|
||||
List<Stop> _filteredStops(List<Stop> stops) {
|
||||
if (_searchQuery.isEmpty) return stops;
|
||||
final q = _searchQuery.toLowerCase();
|
||||
return stops.where((s) {
|
||||
return s.street.toLowerCase().contains(q) ||
|
||||
s.houseNumber.toLowerCase().contains(q) ||
|
||||
'${s.street} ${s.houseNumber}'.toLowerCase().contains(q);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// ── Stop Details ────────────────────────────────────────
|
||||
|
||||
void _showStopDetails(int index, List<Stop> filteredStops) {
|
||||
final stop = filteredStops[index];
|
||||
final notesCtrl = TextEditingController(text: stop.notes);
|
||||
final isDelivered = _deliveryProvider.todayDeliveredIds.contains(stop.id);
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (ctx) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
bottom: MediaQuery.of(ctx).viewInsets.bottom + 20,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${stop.street} ${stop.houseNumber}',
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (stop.newspapers.isNotEmpty)
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: stop.newspapers
|
||||
.map((np) => Chip(
|
||||
label: Text(np),
|
||||
backgroundColor: _npColors[np] ?? Colors.grey,
|
||||
labelStyle: const TextStyle(color: Colors.white),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: notesCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Notes',
|
||||
hintText: 'Dog, gate code...',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
_deliveryProvider.saveNotes(stop.id!, notesCtrl.text);
|
||||
_deliveryProvider.toggleDelivered(stop.id!);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isDelivered ? Colors.orange : Colors.green,
|
||||
),
|
||||
child: Text(isDelivered ? 'Undo Delivery' : 'Mark Delivered'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_deleteStop(stop);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
_deliveryProvider.saveNotes(stop.id!, notesCtrl.text);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('Save Notes'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
).whenComplete(() {
|
||||
_deliveryProvider.saveNotes(stop.id!, notesCtrl.text);
|
||||
notesCtrl.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _deleteStop(Stop stop) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Delete Stop'),
|
||||
content: Text('Delete ${stop.street} ${stop.houseNumber}?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await _deliveryProvider.deleteStop(stop.id!);
|
||||
}
|
||||
}
|
||||
|
||||
void _openBuilder() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BuilderPage(routeId: widget.routeId, onSave: () {
|
||||
_deliveryProvider.loadStops(widget.routeId);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Build ───────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
final stops = _deliveryProvider.stopsWithDeliveryStatus;
|
||||
final filteredStops = _filteredStops(stops);
|
||||
final done = _deliveryProvider.deliveredCount;
|
||||
final total = _deliveryProvider.totalStops;
|
||||
final navMode = _deliveryProvider.navigateMode;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('${widget.name} ($done/$total)'),
|
||||
actions: [
|
||||
// Navigate mode toggle
|
||||
IconButton(
|
||||
icon: Icon(navMode ? Icons.stop_circle : Icons.navigation),
|
||||
onPressed: () {
|
||||
if (navMode) {
|
||||
_deliveryProvider.stopNavigation();
|
||||
} else {
|
||||
_deliveryProvider.startNavigation();
|
||||
}
|
||||
},
|
||||
tooltip: navMode ? 'Stop Navigation' : 'Start Navigation',
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: _handleMenuAction,
|
||||
itemBuilder: (_) => [
|
||||
const PopupMenuItem(value: 'stats', child: Text('📊 Statistics')),
|
||||
const PopupMenuItem(value: 'export', child: Text('📤 Export GPX')),
|
||||
const PopupMenuItem(value: 'reset', child: Text('🔄 Reset Today')),
|
||||
const PopupMenuItem(value: 'optimize', child: Text('⚡ Optimize Route')),
|
||||
const PopupMenuItem(value: 'refresh', child: Text('📍 Refresh Coords')),
|
||||
const PopupMenuItem(value: 'settings', child: Text('⚙️ Settings')),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _buildBody(stops, filteredStops, navMode),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _openBuilder,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleMenuAction(String action) {
|
||||
switch (action) {
|
||||
case 'stats':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => StatisticsPage(
|
||||
deliveryProvider: _deliveryProvider,
|
||||
routeName: widget.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 'export':
|
||||
_exportGpx();
|
||||
break;
|
||||
case 'reset':
|
||||
_resetToday();
|
||||
break;
|
||||
case 'optimize':
|
||||
_optimizeStops();
|
||||
break;
|
||||
case 'refresh':
|
||||
_refreshCoordinates();
|
||||
break;
|
||||
case 'settings':
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => SettingsPage(appState: context.read<AppState>()),
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody(List<Stop> allStops, List<Stop> filteredStops, bool navMode) {
|
||||
if (_deliveryProvider.isLoading) {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [CircularProgressIndicator(), SizedBox(height: 16), Text('Loading stops...')],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_deliveryProvider.error != null) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text(_deliveryProvider.error!, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _deliveryProvider.loadStops(widget.routeId),
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Newspaper counts header
|
||||
if (allStops.isNotEmpty) _buildNewspaperHeader(),
|
||||
|
||||
// Search bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search by street or house number...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() => _searchQuery = '');
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
isDense: true,
|
||||
),
|
||||
onChanged: (v) => setState(() => _searchQuery = v),
|
||||
),
|
||||
),
|
||||
|
||||
// Navigation mode bar
|
||||
if (navMode) _buildNavigationBar(allStops),
|
||||
|
||||
// Map
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: const MapOptions(
|
||||
initialCenter: LatLng(51.428, 4.330),
|
||||
initialZoom: 14,
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate: context.read<AppState>().tileUrl,
|
||||
subdomains: context.read<AppState>().tileSubdomains.split(''),
|
||||
userAgentPackageName: 'com.delivery.route',
|
||||
),
|
||||
MarkerLayer(markers: [
|
||||
...filteredStops.asMap().entries.map((e) {
|
||||
final stop = e.value;
|
||||
final isDelivered =
|
||||
_deliveryProvider.todayDeliveredIds.contains(stop.id);
|
||||
final isCurrentNav =
|
||||
navMode && e.key == _deliveryProvider.navigateIndex;
|
||||
|
||||
return Marker(
|
||||
point: stop.location,
|
||||
width: isCurrentNav ? 32 : 24,
|
||||
height: isCurrentNav ? 32 : 24,
|
||||
child: GestureDetector(
|
||||
onTap: () => _showStopDetails(e.key, filteredStops),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDelivered
|
||||
? Colors.green.withValues(alpha: 0.6)
|
||||
: (stop.newspapers.isNotEmpty
|
||||
? (_npColors[stop.newspapers.first] ?? Colors.grey)
|
||||
: Colors.grey),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isCurrentNav ? Colors.yellow : Colors.white,
|
||||
width: isCurrentNav ? 3 : 2,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${stop.sequence + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
if (_userPosition != null)
|
||||
Marker(
|
||||
point: LatLng(_userPosition!.latitude, _userPosition!.longitude),
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
if (_showRoute && _routePoints.isNotEmpty)
|
||||
PolylineLayer(
|
||||
polylines: [
|
||||
Polyline(points: _routePoints, color: Colors.blue, strokeWidth: 4.0)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Route distance badge
|
||||
if (_showRoute && _routeDistanceKm > 0)
|
||||
Positioned(
|
||||
top: 8,
|
||||
right: 8,
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.route, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${_routeDistanceKm.toStringAsFixed(1)} km',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Loading overlay
|
||||
if (_isLoadingRoute)
|
||||
Container(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
child: const Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text('Calculating route...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Stops list
|
||||
SizedBox(
|
||||
height: 220,
|
||||
child: filteredStops.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_searchQuery.isNotEmpty ? Icons.search_off : Icons.add_location_alt,
|
||||
size: 48,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_searchQuery.isNotEmpty ? 'No matching stops' : 'No stops',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
if (_searchQuery.isEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _openBuilder,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add Stops'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: filteredStops.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final stop = filteredStops[i];
|
||||
final isDelivered =
|
||||
_deliveryProvider.todayDeliveredIds.contains(stop.id);
|
||||
final isCurrentNav =
|
||||
navMode && i == _deliveryProvider.navigateIndex;
|
||||
|
||||
return ListTile(
|
||||
dense: true,
|
||||
selected: isCurrentNav,
|
||||
selectedTileColor: Colors.yellow.withValues(alpha: 0.15),
|
||||
leading: CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: isDelivered
|
||||
? Colors.green
|
||||
: (stop.newspapers.isNotEmpty
|
||||
? (_npColors[stop.newspapers.first] ?? Colors.grey)
|
||||
: Colors.grey),
|
||||
child: Text(
|
||||
'${stop.sequence + 1}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'${stop.street} ${stop.houseNumber}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
subtitle: _buildSubtitle(stop),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (stop.newspapers.length > 1)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Text(
|
||||
'${stop.newspapers.length}📰',
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: isDelivered,
|
||||
onChanged: (_) => _deliveryProvider.toggleDelivered(stop.id!),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _showStopDetails(i, filteredStops),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ── Newspaper Header ────────────────────────────────────
|
||||
|
||||
Widget _buildNewspaperHeader() {
|
||||
final counts = _deliveryProvider.newspaperCounts;
|
||||
final total = _deliveryProvider.totalNewspapers;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.newspaper, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text('$total papers', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: counts.entries.map((e) {
|
||||
final color = _npColors[e.key] ?? Colors.grey;
|
||||
return Text(
|
||||
'${e.key}:${e.value}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
// Route show/hide button
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (_showRoute) {
|
||||
setState(() => _showRoute = false);
|
||||
} else {
|
||||
_fetchRoute();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_showRoute ? Icons.route : Icons.route_outlined,
|
||||
size: 18,
|
||||
color: _showRoute ? Colors.blue : null,
|
||||
),
|
||||
if (_showRoute && _routeDistanceKm > 0)
|
||||
Text(
|
||||
' ${_routeDistanceKm.toStringAsFixed(1)}km',
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Navigation Bar ──────────────────────────────────────
|
||||
|
||||
Widget _buildNavigationBar(List<Stop> allStops) {
|
||||
final idx = _deliveryProvider.navigateIndex;
|
||||
final total = allStops.length;
|
||||
final currentStop = _deliveryProvider.currentNavigateStop;
|
||||
final delivered = _deliveryProvider.deliveredCount;
|
||||
final progress = _deliveryProvider.progressPercent;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Progress bar
|
||||
LinearProgressIndicator(
|
||||
value: progress,
|
||||
minHeight: 6,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
// Stop counter
|
||||
Text(
|
||||
'Stop ${idx + 1} of $total',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Delivered count
|
||||
Text(
|
||||
'$delivered delivered',
|
||||
style: TextStyle(color: Colors.grey[600], fontSize: 13),
|
||||
),
|
||||
const Spacer(),
|
||||
// Navigation buttons
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
onPressed: idx > 0 ? _deliveryProvider.previousStop : null,
|
||||
iconSize: 28,
|
||||
),
|
||||
// Mark delivered button
|
||||
if (currentStop != null)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _deliveryProvider.toggleDelivered(currentStop.id!),
|
||||
icon: Icon(
|
||||
_deliveryProvider.todayDeliveredIds.contains(currentStop.id)
|
||||
? Icons.undo
|
||||
: Icons.check,
|
||||
size: 18,
|
||||
),
|
||||
label: Text(
|
||||
_deliveryProvider.todayDeliveredIds.contains(currentStop.id)
|
||||
? 'Undo'
|
||||
: 'Deliver',
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
_deliveryProvider.todayDeliveredIds.contains(currentStop.id)
|
||||
? Colors.orange
|
||||
: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
onPressed: idx < total - 1 ? _deliveryProvider.nextStop : null,
|
||||
iconSize: 28,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (currentStop != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${currentStop.street} ${currentStop.houseNumber}',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
if (currentStop.newspapers.isNotEmpty)
|
||||
Text(
|
||||
currentStop.newspapers.join(', '),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildSubtitle(Stop stop) {
|
||||
final parts = <String>[];
|
||||
if (stop.newspapers.isNotEmpty) parts.add(stop.newspapers.join(', '));
|
||||
if (stop.notes.isNotEmpty) parts.add('📝 ${stop.notes}');
|
||||
if (parts.isEmpty) return null;
|
||||
return Text(parts.join(' • '), style: const TextStyle(fontSize: 12));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../services/database.dart';
|
||||
import '../providers/app_state.dart';
|
||||
import 'route_page.dart';
|
||||
import 'settings_page.dart';
|
||||
|
||||
class RoutesPage extends StatefulWidget {
|
||||
const RoutesPage({super.key});
|
||||
@override
|
||||
State<RoutesPage> createState() => _RoutesPageState();
|
||||
}
|
||||
|
||||
class _RoutesPageState extends State<RoutesPage> {
|
||||
final _db = DatabaseService();
|
||||
List<Map<String, dynamic>> _routes = [];
|
||||
bool _loading = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initDb();
|
||||
}
|
||||
|
||||
Future<void> _initDb() async {
|
||||
try {
|
||||
await _db.database;
|
||||
await _loadRoutes();
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = 'Failed to initialize database: $e';
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadRoutes() async {
|
||||
try {
|
||||
final r = await _db.getRoutes();
|
||||
setState(() {
|
||||
_routes = r;
|
||||
_loading = false;
|
||||
_error = null;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = 'Failed to load routes: $e';
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _addRoute() {
|
||||
final ctrl = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('New Route'),
|
||||
content: TextField(
|
||||
controller: ctrl,
|
||||
decoration: const InputDecoration(labelText: 'Route name', hintText: 'e.g., Morning Route'),
|
||||
autofocus: true,
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Cancel')),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (ctrl.text.trim().isNotEmpty) {
|
||||
await _db.insertRoute(ctrl.text.trim());
|
||||
await _loadRoutes();
|
||||
}
|
||||
if (ctx.mounted) Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text('Create'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteRoute(int routeId, String routeName) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Delete Route'),
|
||||
content: Text('Delete "$routeName" and all its stops?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await _db.deleteRoute(routeId);
|
||||
await _loadRoutes();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('My Routes'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => SettingsPage(appState: context.read<AppState>()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _buildBody(),
|
||||
floatingActionButton: FloatingActionButton(onPressed: _addRoute, child: const Icon(Icons.add)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (_loading) {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text('Initializing database...'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_error != null) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text(_error!, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
setState(() => _loading = true);
|
||||
_initDb();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_routes.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.route, size: 64, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
const Text('No routes yet', style: TextStyle(fontSize: 18, color: Colors.grey)),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _addRoute,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Create Route'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: _loadRoutes,
|
||||
child: ListView.builder(
|
||||
itemCount: _routes.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final route = _routes[i];
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(child: Icon(Icons.route)),
|
||||
title: Text(route['name'] as String, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
subtitle: Text(
|
||||
route['created_at'] != null
|
||||
? _formatDate(route['created_at'] as String)
|
||||
: '',
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||
onPressed: () => _deleteRoute(route['id'] as int, route['name'] as String),
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => RoutePage(routeId: route['id'] as int, name: route['name'] as String),
|
||||
),
|
||||
).then((_) => _loadRoutes()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(String iso) {
|
||||
try {
|
||||
final dt = DateTime.parse(iso);
|
||||
return '${dt.day}/${dt.month}/${dt.year} ${dt.hour}:${dt.minute.toString().padLeft(2, '0')}';
|
||||
} catch (_) {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../providers/app_state.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
final AppState appState;
|
||||
|
||||
const SettingsPage({super.key, required this.appState});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
body: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ── Appearance ──────────────────────────────
|
||||
_sectionHeader('Appearance'),
|
||||
SwitchListTile(
|
||||
title: const Text('Dark Mode'),
|
||||
subtitle: const Text('Use dark theme with dark map tiles'),
|
||||
value: appState.darkMode,
|
||||
onChanged: (v) => appState.setDarkMode(v),
|
||||
secondary: Icon(
|
||||
appState.darkMode ? Icons.dark_mode : Icons.light_mode,
|
||||
),
|
||||
),
|
||||
|
||||
const Divider(),
|
||||
|
||||
// ── Map Style ───────────────────────────────
|
||||
_sectionHeader('Map Style'),
|
||||
_mapStyleTile(context, 'voyager', 'CartoDB Voyager',
|
||||
'Colorful, detailed map (recommended)', Icons.map),
|
||||
_mapStyleTile(context, 'osm', 'OpenStreetMap',
|
||||
'Standard OSM tiles', Icons.public),
|
||||
_mapStyleTile(context, 'positron', 'CartoDB Positron',
|
||||
'Clean, light minimal style', Icons.brightness_7),
|
||||
_mapStyleTile(context, 'dark', 'CartoDB Dark',
|
||||
'Dark map tiles', Icons.brightness_3),
|
||||
|
||||
const Divider(),
|
||||
|
||||
// ── About ───────────────────────────────────
|
||||
_sectionHeader('About'),
|
||||
const ListTile(
|
||||
leading: Icon(Icons.info_outline),
|
||||
title: Text('Delivery Route App'),
|
||||
subtitle: Text('Newspaper delivery route management'),
|
||||
),
|
||||
const ListTile(
|
||||
leading: Icon(Icons.newspaper),
|
||||
title: Text('Supported Newspapers'),
|
||||
subtitle: Text('BN, AD, TEL, VK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sectionHeader(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _mapStyleTile(
|
||||
BuildContext context,
|
||||
String style,
|
||||
String title,
|
||||
String subtitle,
|
||||
IconData icon,
|
||||
) {
|
||||
final isSelected = appState.mapStyle == style;
|
||||
return RadioListTile<String>(
|
||||
title: Text(title),
|
||||
subtitle: Text(subtitle),
|
||||
value: style,
|
||||
groupValue: appState.mapStyle,
|
||||
onChanged: (v) {
|
||||
if (v != null) appState.setMapStyle(v);
|
||||
},
|
||||
secondary: Icon(icon, color: isSelected ? Colors.blue : null),
|
||||
activeColor: Colors.blue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../providers/delivery_provider.dart';
|
||||
import '../models/delivery_history.dart';
|
||||
|
||||
class StatisticsPage extends StatefulWidget {
|
||||
final DeliveryProvider deliveryProvider;
|
||||
final String routeName;
|
||||
|
||||
const StatisticsPage({
|
||||
super.key,
|
||||
required this.deliveryProvider,
|
||||
required this.routeName,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatisticsPage> createState() => _StatisticsPageState();
|
||||
}
|
||||
|
||||
class _StatisticsPageState extends State<StatisticsPage> {
|
||||
List<DailyStats> _stats = [];
|
||||
int _totalDeliveries = 0;
|
||||
int _weeklyDeliveries = 0;
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadStats();
|
||||
}
|
||||
|
||||
Future<void> _loadStats() async {
|
||||
final stats = await widget.deliveryProvider.getStats(days: 30);
|
||||
final total = await widget.deliveryProvider.totalDeliveries;
|
||||
final weekly = await widget.deliveryProvider.weeklyDeliveries;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_stats = stats;
|
||||
_totalDeliveries = total;
|
||||
_weeklyDeliveries = weekly;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = widget.deliveryProvider;
|
||||
final todayDelivered = provider.deliveredCount;
|
||||
final totalStops = provider.totalStops;
|
||||
final newspaperCounts = provider.newspaperCounts;
|
||||
final totalNewspapers = provider.totalNewspapers;
|
||||
|
||||
// Calculate averages
|
||||
final daysWithDeliveries = _stats.where((s) => s.deliveredCount > 0).length;
|
||||
final avgPerDay = daysWithDeliveries > 0
|
||||
? (_stats.fold<int>(0, (sum, s) => sum + s.deliveredCount) / daysWithDeliveries)
|
||||
: 0.0;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Statistics - ${widget.routeName}'),
|
||||
),
|
||||
body: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: RefreshIndicator(
|
||||
onRefresh: _loadStats,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Today's progress
|
||||
_buildCard(
|
||||
icon: Icons.today,
|
||||
title: 'Today',
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: totalStops > 0 ? todayDelivered / totalStops : 0,
|
||||
minHeight: 10,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'$todayDelivered / $totalStops stops delivered',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (totalStops > 0)
|
||||
Text(
|
||||
'${(todayDelivered / totalStops * 100).toStringAsFixed(0)}% complete',
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Newspaper counts
|
||||
_buildCard(
|
||||
icon: Icons.newspaper,
|
||||
title: 'Newspapers',
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'$totalNewspapers total newspapers',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.center,
|
||||
children: newspaperCounts.entries.map((e) {
|
||||
final color = _npColor(e.key);
|
||||
return Chip(
|
||||
avatar: CircleAvatar(
|
||||
backgroundColor: color,
|
||||
radius: 8,
|
||||
),
|
||||
label: Text('${e.key}: ${e.value}'),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Summary stats
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
icon: Icons.calendar_view_week,
|
||||
label: 'This Week',
|
||||
value: '$_weeklyDeliveries',
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
icon: Icons.all_inclusive,
|
||||
label: 'Total All Time',
|
||||
value: '$_totalDeliveries',
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
icon: Icons.analytics,
|
||||
label: 'Avg/Day',
|
||||
value: avgPerDay.toStringAsFixed(1),
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
icon: Icons.date_range,
|
||||
label: 'Active Days',
|
||||
value: '$daysWithDeliveries',
|
||||
color: Colors.purple,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Daily history
|
||||
_buildCard(
|
||||
icon: Icons.history,
|
||||
title: 'Delivery History (Last 30 Days)',
|
||||
child: _stats.isEmpty
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'No deliveries recorded yet',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
children: _stats.map((stat) {
|
||||
final date = DateTime.tryParse(stat.date);
|
||||
final dateStr = date != null
|
||||
? '${date.day}/${date.month}/${date.year}'
|
||||
: stat.date;
|
||||
final pct = stat.totalCount > 0
|
||||
? (stat.deliveredCount / stat.totalCount * 100)
|
||||
.toStringAsFixed(0)
|
||||
: '0';
|
||||
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: stat.deliveredCount == stat.totalCount
|
||||
? Colors.green
|
||||
: Colors.orange,
|
||||
child: Text(
|
||||
'$pct%',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(dateStr),
|
||||
subtitle: Text(
|
||||
'${stat.deliveredCount} / ${stat.totalCount} stops',
|
||||
),
|
||||
trailing: stat.deliveredCount == stat.totalCount
|
||||
? const Icon(Icons.check_circle, color: Colors.green)
|
||||
: null,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required Widget child,
|
||||
}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
child,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String value,
|
||||
required Color color,
|
||||
}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 28),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _npColor(String np) {
|
||||
switch (np) {
|
||||
case 'BN':
|
||||
return Colors.red;
|
||||
case 'AD':
|
||||
return Colors.blue;
|
||||
case 'TEL':
|
||||
return Colors.orange;
|
||||
case 'VK':
|
||||
return Colors.purple;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../services/database.dart';
|
||||
|
||||
class AppState extends ChangeNotifier {
|
||||
final DatabaseService _db = DatabaseService();
|
||||
|
||||
bool _darkMode = false;
|
||||
String _mapStyle = 'voyager'; // 'voyager', 'osm', 'dark', 'positron'
|
||||
bool _isLoaded = false;
|
||||
|
||||
bool get darkMode => _darkMode;
|
||||
String get mapStyle => _mapStyle;
|
||||
bool get isLoaded => _isLoaded;
|
||||
|
||||
ThemeData get theme => _darkMode ? _darkTheme : _lightTheme;
|
||||
|
||||
String get tileUrl {
|
||||
if (_darkMode) {
|
||||
return 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png';
|
||||
}
|
||||
switch (_mapStyle) {
|
||||
case 'osm':
|
||||
return 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
case 'dark':
|
||||
return 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png';
|
||||
case 'positron':
|
||||
return 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
|
||||
case 'voyager':
|
||||
default:
|
||||
return 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
|
||||
}
|
||||
}
|
||||
|
||||
String get tileSubdomains => 'abcd';
|
||||
|
||||
Future<void> loadSettings() async {
|
||||
_darkMode = await _db.getSetting('dark_mode', false);
|
||||
_mapStyle = await _db.getSetting('map_style', 'voyager');
|
||||
_isLoaded = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setDarkMode(bool value) async {
|
||||
_darkMode = value;
|
||||
notifyListeners();
|
||||
await _db.setSetting('dark_mode', value);
|
||||
}
|
||||
|
||||
Future<void> setMapStyle(String style) async {
|
||||
_mapStyle = style;
|
||||
notifyListeners();
|
||||
await _db.setSetting('map_style', style);
|
||||
}
|
||||
|
||||
// ── Themes ──────────────────────────────────────────────
|
||||
|
||||
static final _lightTheme = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
|
||||
static final _darkTheme = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: const Color(0xFF121212),
|
||||
cardColor: const Color(0xFF1E1E1E),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: Color(0xFF1E1E1E),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
bottomSheetTheme: const BottomSheetThemeData(
|
||||
backgroundColor: Color(0xFF1E1E1E),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../models/stop.dart';
|
||||
import '../models/delivery_history.dart';
|
||||
import '../services/database.dart';
|
||||
|
||||
class DeliveryProvider extends ChangeNotifier {
|
||||
final DatabaseService _db = DatabaseService();
|
||||
|
||||
List<Stop> _stops = [];
|
||||
Set<int> _todayDeliveredIds = {};
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
int? _currentRouteId;
|
||||
int _navigateIndex = 0;
|
||||
bool _navigateMode = false;
|
||||
|
||||
List<Stop> get stops => _stops;
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
bool get navigateMode => _navigateMode;
|
||||
Set<int> get todayDeliveredIds => _todayDeliveredIds;
|
||||
int get navigateIndex => _navigateIndex;
|
||||
|
||||
/// Stops filtered to show delivered status based on TODAY's history.
|
||||
List<Stop> get stopsWithDeliveryStatus {
|
||||
return _stops.map((s) {
|
||||
final isDeliveredToday = _todayDeliveredIds.contains(s.id);
|
||||
return s.copyWith(delivered: isDeliveredToday);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
int get deliveredCount => _todayDeliveredIds.length;
|
||||
int get totalStops => _stops.length;
|
||||
double get progressPercent =>
|
||||
_stops.isEmpty ? 0.0 : _todayDeliveredIds.length / _stops.length;
|
||||
|
||||
/// Newspaper counts breakdown
|
||||
Map<String, int> get newspaperCounts {
|
||||
final counts = <String, int>{};
|
||||
for (final stop in _stops) {
|
||||
for (final np in stop.newspapers) {
|
||||
counts[np] = (counts[np] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
int get totalNewspapers {
|
||||
int total = 0;
|
||||
for (final stop in _stops) {
|
||||
total += stop.newspapers.length;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// Current stop in navigation mode
|
||||
Stop? get currentNavigateStop {
|
||||
if (!_navigateMode || _stops.isEmpty) return null;
|
||||
if (_navigateIndex >= _stops.length) return null;
|
||||
return stopsWithDeliveryStatus[_navigateIndex];
|
||||
}
|
||||
|
||||
/// Get the next undelivered stop index for navigation
|
||||
int get nextUndeliveredIndex {
|
||||
final orderedStops = stopsWithDeliveryStatus;
|
||||
for (var i = 0; i < orderedStops.length; i++) {
|
||||
if (!orderedStops[i].delivered) return i;
|
||||
}
|
||||
return -1; // all delivered
|
||||
}
|
||||
|
||||
// ── Load ────────────────────────────────────────────────
|
||||
|
||||
Future<void> loadStops(int routeId) async {
|
||||
_currentRouteId = routeId;
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_stops = await _db.getStops(routeId);
|
||||
_todayDeliveredIds = await _db.getTodayDeliveredStopIds(routeId);
|
||||
_isLoading = false;
|
||||
_error = null;
|
||||
} catch (e) {
|
||||
_error = 'Failed to load stops: $e';
|
||||
_isLoading = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ── Toggle Delivery ─────────────────────────────────────
|
||||
|
||||
Future<void> toggleDelivered(int stopId) async {
|
||||
if (_currentRouteId == null) return;
|
||||
|
||||
final isDelivered = _todayDeliveredIds.contains(stopId);
|
||||
if (isDelivered) {
|
||||
await _db.removeDeliveryRecord(_currentRouteId!, stopId);
|
||||
_todayDeliveredIds.remove(stopId);
|
||||
} else {
|
||||
await _db.recordDelivery(_currentRouteId!, stopId);
|
||||
_todayDeliveredIds.add(stopId);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ── Reset Today ─────────────────────────────────────────
|
||||
|
||||
Future<void> resetToday() async {
|
||||
if (_currentRouteId == null) return;
|
||||
await _db.resetToday(_currentRouteId!);
|
||||
_todayDeliveredIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ── Navigation Mode ─────────────────────────────────────
|
||||
|
||||
void startNavigation() {
|
||||
_navigateMode = true;
|
||||
_navigateIndex = nextUndeliveredIndex >= 0 ? nextUndeliveredIndex : 0;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void stopNavigation() {
|
||||
_navigateMode = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void nextStop() {
|
||||
if (_navigateIndex < _stops.length - 1) {
|
||||
_navigateIndex++;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void previousStop() {
|
||||
if (_navigateIndex > 0) {
|
||||
_navigateIndex--;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void goToStop(int index) {
|
||||
if (index >= 0 && index < _stops.length) {
|
||||
_navigateIndex = index;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Statistics ──────────────────────────────────────────
|
||||
|
||||
Future<List<DailyStats>> getStats({int days = 30}) async {
|
||||
if (_currentRouteId == null) return [];
|
||||
return _db.getDeliveryStats(_currentRouteId!, days: days);
|
||||
}
|
||||
|
||||
Future<int> get totalDeliveries async {
|
||||
if (_currentRouteId == null) return 0;
|
||||
return _db.getTotalDeliveries(_currentRouteId!);
|
||||
}
|
||||
|
||||
Future<int> get weeklyDeliveries async {
|
||||
if (_currentRouteId == null) return 0;
|
||||
return _db.getWeeklyDeliveryCount(_currentRouteId!);
|
||||
}
|
||||
|
||||
// ── Notes ───────────────────────────────────────────────
|
||||
|
||||
Future<void> saveNotes(int stopId, String notes) async {
|
||||
await _db.updateStopNotes(stopId, notes);
|
||||
final idx = _stops.indexWhere((s) => s.id == stopId);
|
||||
if (idx >= 0) {
|
||||
_stops[idx] = _stops[idx].copyWith(notes: notes);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete Stop ─────────────────────────────────────────
|
||||
|
||||
Future<void> deleteStop(int stopId) async {
|
||||
await _db.deleteStop(stopId);
|
||||
_stops.removeWhere((s) => s.id == stopId);
|
||||
_todayDeliveredIds.remove(stopId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ── Reorder ─────────────────────────────────────────────
|
||||
|
||||
Future<void> reorderStops(List<Stop> newOrder) async {
|
||||
await _db.reorderStops(newOrder);
|
||||
_stops = newOrder;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ── Update Coordinates ──────────────────────────────────
|
||||
|
||||
Future<void> updateCoords(int stopId, double lat, double lng) async {
|
||||
await _db.updateStopCoords(stopId, lat, lng);
|
||||
final idx = _stops.indexWhere((s) => s.id == stopId);
|
||||
if (idx >= 0) {
|
||||
_stops[idx] = _stops[idx].copyWith(lat: lat, lng: lng);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import '../models/stop.dart';
|
||||
import '../models/delivery_history.dart';
|
||||
|
||||
class DatabaseService {
|
||||
static final DatabaseService _instance = DatabaseService._internal();
|
||||
factory DatabaseService() => _instance;
|
||||
DatabaseService._internal();
|
||||
|
||||
Database? _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null && _database!.isOpen) return _database!;
|
||||
_database = await _initDatabase();
|
||||
return _database!;
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
final dbPath = await getDatabasesPath();
|
||||
return await openDatabase(
|
||||
p.join(dbPath, 'delivery.db'),
|
||||
version: 3,
|
||||
onCreate: (db, version) async {
|
||||
await _createTables(db);
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
if (oldVersion < 2) {
|
||||
await db.execute('ALTER TABLE stops ADD COLUMN notes TEXT DEFAULT \'\'');
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
await _createDeliveryHistoryTable(db);
|
||||
await _createSettingsTable(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _createTables(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE routes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
created_at TEXT
|
||||
)
|
||||
''');
|
||||
await db.execute('''
|
||||
CREATE TABLE stops (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
route_id INTEGER,
|
||||
street TEXT,
|
||||
house_number TEXT,
|
||||
newspapers TEXT,
|
||||
lat REAL,
|
||||
lng REAL,
|
||||
delivered INTEGER DEFAULT 0,
|
||||
sequence INTEGER DEFAULT 0,
|
||||
notes TEXT DEFAULT '',
|
||||
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
await _createDeliveryHistoryTable(db);
|
||||
await _createSettingsTable(db);
|
||||
}
|
||||
|
||||
Future<void> _createDeliveryHistoryTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS delivery_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
route_id INTEGER,
|
||||
stop_id INTEGER,
|
||||
date TEXT,
|
||||
delivered_at TEXT,
|
||||
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (stop_id) REFERENCES stops(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_delivery_history_date ON delivery_history(date)',
|
||||
);
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_delivery_history_route ON delivery_history(route_id)',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _createSettingsTable(Database db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
// ── Settings ────────────────────────────────────────────
|
||||
|
||||
Future<T> getSetting<T>(String key, T defaultValue) async {
|
||||
final db = await database;
|
||||
final rows = await db.query('settings', where: 'key = ?', whereArgs: [key]);
|
||||
if (rows.isEmpty) return defaultValue;
|
||||
final raw = rows.first['value'] as String;
|
||||
if (T == bool) return (raw == 'true') as T;
|
||||
if (T == int) return int.parse(raw) as T;
|
||||
if (T == double) return double.parse(raw) as T;
|
||||
return raw as T;
|
||||
}
|
||||
|
||||
Future<void> setSetting(String key, dynamic value) async {
|
||||
final db = await database;
|
||||
await db.insert(
|
||||
'settings',
|
||||
{'key': key, 'value': value.toString()},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
// ── Routes ──────────────────────────────────────────────
|
||||
|
||||
Future<List<Map<String, dynamic>>> getRoutes() async {
|
||||
final db = await database;
|
||||
return db.query('routes', orderBy: 'created_at DESC');
|
||||
}
|
||||
|
||||
Future<int> insertRoute(String name) async {
|
||||
final db = await database;
|
||||
return db.insert('routes', {
|
||||
'name': name,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteRoute(int routeId) async {
|
||||
final db = await database;
|
||||
await db.delete('delivery_history', where: 'route_id = ?', whereArgs: [routeId]);
|
||||
await db.delete('stops', where: 'route_id = ?', whereArgs: [routeId]);
|
||||
await db.delete('routes', where: 'id = ?', whereArgs: [routeId]);
|
||||
}
|
||||
|
||||
// ── Stops ───────────────────────────────────────────────
|
||||
|
||||
Future<List<Stop>> getStops(int routeId) async {
|
||||
final db = await database;
|
||||
final maps = await db.query(
|
||||
'stops',
|
||||
where: 'route_id = ?',
|
||||
whereArgs: [routeId],
|
||||
orderBy: 'sequence',
|
||||
);
|
||||
return maps.map(Stop.fromMap).toList();
|
||||
}
|
||||
|
||||
Future<int> insertStop(Stop stop, int routeId) async {
|
||||
final db = await database;
|
||||
final map = stop.toMap();
|
||||
map['route_id'] = routeId;
|
||||
return db.insert('stops', map);
|
||||
}
|
||||
|
||||
Future<void> updateStop(Stop stop) async {
|
||||
final db = await database;
|
||||
await db.update('stops', stop.toMap(), where: 'id = ?', whereArgs: [stop.id]);
|
||||
}
|
||||
|
||||
Future<void> deleteStop(int stopId) async {
|
||||
final db = await database;
|
||||
await db.delete('delivery_history', where: 'stop_id = ?', whereArgs: [stopId]);
|
||||
await db.delete('stops', where: 'id = ?', whereArgs: [stopId]);
|
||||
}
|
||||
|
||||
Future<void> updateStopSequence(int stopId, int sequence) async {
|
||||
final db = await database;
|
||||
await db.update('stops', {'sequence': sequence}, where: 'id = ?', whereArgs: [stopId]);
|
||||
}
|
||||
|
||||
Future<void> updateStopDelivered(int stopId, bool delivered) async {
|
||||
final db = await database;
|
||||
await db.update('stops', {'delivered': delivered ? 1 : 0}, where: 'id = ?', whereArgs: [stopId]);
|
||||
}
|
||||
|
||||
Future<void> updateStopNotes(int stopId, String notes) async {
|
||||
final db = await database;
|
||||
await db.update('stops', {'notes': notes}, where: 'id = ?', whereArgs: [stopId]);
|
||||
}
|
||||
|
||||
Future<void> updateStopCoords(int stopId, double lat, double lng) async {
|
||||
final db = await database;
|
||||
await db.update('stops', {'lat': lat, 'lng': lng}, where: 'id = ?', whereArgs: [stopId]);
|
||||
}
|
||||
|
||||
Future<int?> getMaxSequence(int routeId) async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery(
|
||||
'SELECT MAX(sequence) as m FROM stops WHERE route_id = ?',
|
||||
[routeId],
|
||||
);
|
||||
return result.first['m'] as int?;
|
||||
}
|
||||
|
||||
Future<void> reorderStops(List<Stop> stops) async {
|
||||
final db = await database;
|
||||
final batch = db.batch();
|
||||
for (var i = 0; i < stops.length; i++) {
|
||||
batch.update('stops', {'sequence': i}, where: 'id = ?', whereArgs: [stops[i].id]);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
|
||||
// ── Delivery History ────────────────────────────────────
|
||||
|
||||
String _today() {
|
||||
final now = DateTime.now();
|
||||
return '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
Future<void> recordDelivery(int routeId, int stopId) async {
|
||||
final db = await database;
|
||||
// Check if already recorded today
|
||||
final existing = await db.query(
|
||||
'delivery_history',
|
||||
where: 'route_id = ? AND stop_id = ? AND date = ?',
|
||||
whereArgs: [routeId, stopId, _today()],
|
||||
);
|
||||
if (existing.isEmpty) {
|
||||
await db.insert('delivery_history', {
|
||||
'route_id': routeId,
|
||||
'stop_id': stopId,
|
||||
'date': _today(),
|
||||
'delivered_at': DateTime.now().toIso8601String(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeDeliveryRecord(int routeId, int stopId) async {
|
||||
final db = await database;
|
||||
await db.delete(
|
||||
'delivery_history',
|
||||
where: 'route_id = ? AND stop_id = ? AND date = ?',
|
||||
whereArgs: [routeId, stopId, _today()],
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the set of stop IDs delivered today for a route.
|
||||
Future<Set<int>> getTodayDeliveredStopIds(int routeId) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
'delivery_history',
|
||||
columns: ['stop_id'],
|
||||
where: 'route_id = ? AND date = ?',
|
||||
whereArgs: [routeId, _today()],
|
||||
);
|
||||
return rows.map((r) => r['stop_id'] as int).toSet();
|
||||
}
|
||||
|
||||
/// Reset today's deliveries for a route (mark all stops undelivered).
|
||||
Future<void> resetToday(int routeId) async {
|
||||
final db = await database;
|
||||
await db.delete(
|
||||
'delivery_history',
|
||||
where: 'route_id = ? AND date = ?',
|
||||
whereArgs: [routeId, _today()],
|
||||
);
|
||||
await db.update('stops', {'delivered': 0}, where: 'route_id = ?', whereArgs: [routeId]);
|
||||
}
|
||||
|
||||
/// Get all unique delivery dates for a route.
|
||||
Future<List<String>> getDeliveryDates(int routeId) async {
|
||||
final db = await database;
|
||||
final rows = await db.rawQuery(
|
||||
'SELECT DISTINCT date FROM delivery_history WHERE route_id = ? ORDER BY date DESC',
|
||||
[routeId],
|
||||
);
|
||||
return rows.map((r) => r['date'] as String).toList();
|
||||
}
|
||||
|
||||
/// Get delivery count for a specific date and route.
|
||||
Future<int> getDeliveryCountForDate(int routeId, String date) async {
|
||||
final db = await database;
|
||||
final rows = await db.rawQuery(
|
||||
'SELECT COUNT(*) as cnt FROM delivery_history WHERE route_id = ? AND date = ?',
|
||||
[routeId, date],
|
||||
);
|
||||
return rows.first['cnt'] as int;
|
||||
}
|
||||
|
||||
/// Get delivery stats for a date range.
|
||||
Future<List<DailyStats>> getDeliveryStats(int routeId, {int days = 30}) async {
|
||||
final db = await database;
|
||||
final now = DateTime.now();
|
||||
final startDate = now.subtract(Duration(days: days));
|
||||
final startStr =
|
||||
'${startDate.year}-${startDate.month.toString().padLeft(2, '0')}-${startDate.day.toString().padLeft(2, '0')}';
|
||||
|
||||
// Get total stops for this route
|
||||
final totalRows = await db.rawQuery(
|
||||
'SELECT COUNT(*) as cnt FROM stops WHERE route_id = ?',
|
||||
[routeId],
|
||||
);
|
||||
final totalStops = totalRows.first['cnt'] as int;
|
||||
|
||||
final rows = await db.rawQuery(
|
||||
'''SELECT date, COUNT(*) as cnt
|
||||
FROM delivery_history
|
||||
WHERE route_id = ? AND date >= ?
|
||||
GROUP BY date
|
||||
ORDER BY date DESC''',
|
||||
[routeId, startStr],
|
||||
);
|
||||
|
||||
return rows
|
||||
.map((r) => DailyStats(
|
||||
date: r['date'] as String,
|
||||
deliveredCount: r['cnt'] as int,
|
||||
totalCount: totalStops,
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get total deliveries ever for a route.
|
||||
Future<int> getTotalDeliveries(int routeId) async {
|
||||
final db = await database;
|
||||
final rows = await db.rawQuery(
|
||||
'SELECT COUNT(*) as cnt FROM delivery_history WHERE route_id = ?',
|
||||
[routeId],
|
||||
);
|
||||
return rows.first['cnt'] as int;
|
||||
}
|
||||
|
||||
/// Get delivery count for current week (Mon-Sun).
|
||||
Future<int> getWeeklyDeliveryCount(int routeId) async {
|
||||
final db = await database;
|
||||
final now = DateTime.now();
|
||||
// Find Monday of this week
|
||||
final monday = now.subtract(Duration(days: now.weekday - 1));
|
||||
final mondayStr =
|
||||
'${monday.year}-${monday.month.toString().padLeft(2, '0')}-${monday.day.toString().padLeft(2, '0')}';
|
||||
|
||||
final rows = await db.rawQuery(
|
||||
'SELECT COUNT(*) as cnt FROM delivery_history WHERE route_id = ? AND date >= ?',
|
||||
[routeId, mondayStr],
|
||||
);
|
||||
return rows.first['cnt'] as int;
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
final db = _database;
|
||||
if (db != null && db.isOpen) {
|
||||
await db.close();
|
||||
_database = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../hoogerheide_streets.dart';
|
||||
|
||||
class GeocodingService {
|
||||
static final GeocodingService _instance = GeocodingService._internal();
|
||||
factory GeocodingService() => _instance;
|
||||
GeocodingService._internal();
|
||||
|
||||
static const _nominatimBaseUrl = 'https://nominatim.openstreetmap.org';
|
||||
static const _userAgent = 'DeliveryApp/1.0 (delivery-app-openclaw@users.noreply.github.com)';
|
||||
|
||||
Map<String, dynamic>? _addressCache;
|
||||
|
||||
String _normalize(String name) =>
|
||||
name.replaceAll(RegExp(r'[–—]'), ' ').replaceAll(RegExp(r'\s+'), ' ').trim().toLowerCase();
|
||||
|
||||
Future<Map<String, dynamic>> _loadAddressCache() async {
|
||||
if (_addressCache != null) return _addressCache!;
|
||||
try {
|
||||
final jsonStr = await rootBundle.loadString('assets/hoogerheide_addresses.json');
|
||||
_addressCache = jsonDecode(jsonStr);
|
||||
return _addressCache!;
|
||||
} catch (_) {
|
||||
return {'addresses': []};
|
||||
}
|
||||
}
|
||||
|
||||
/// Geocode a street + house number to LatLng.
|
||||
/// Priority: local JSON cache → Nominatim → hoogerheide_streets.dart → default
|
||||
Future<LatLng> geocode(String street, String houseNum) async {
|
||||
final ns = _normalize(street);
|
||||
final hn = houseNum.trim();
|
||||
|
||||
// 1. Local address cache (fast, no rate limits)
|
||||
try {
|
||||
final cache = await _loadAddressCache();
|
||||
final addresses = cache['addresses'] as List? ?? [];
|
||||
for (final addr in addresses) {
|
||||
final addrStreet = (addr['street'] as String).toLowerCase();
|
||||
final addrNum = addr['housenumber'] as String;
|
||||
if (addrStreet == ns && addrNum == hn) {
|
||||
return LatLng(addr['lat'] as double, addr['lon'] as double);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// 2. Nominatim API
|
||||
try {
|
||||
final query = Uri.encodeComponent('$street $houseNum, Hoogerheide, Netherlands');
|
||||
final url = '$_nominatimBaseUrl/search?format=jsonv2&q=$query&limit=1';
|
||||
final resp = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: {'User-Agent': _userAgent},
|
||||
).timeout(const Duration(seconds: 5));
|
||||
|
||||
if (resp.statusCode == 200) {
|
||||
final data = jsonDecode(resp.body);
|
||||
if (data is List && data.isNotEmpty) {
|
||||
return LatLng(
|
||||
double.parse(data[0]['lat']),
|
||||
double.parse(data[0]['lon']),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// 3. hoogerheide_streets.dart lookup (exact then partial match)
|
||||
final streetResult = _lookupInStreets(street);
|
||||
if (streetResult != null) {
|
||||
// Offset by house number to spread markers along the street
|
||||
final hnInt = int.tryParse(houseNum.replaceAll(RegExp(r'[A-Za-z]'), '')) ?? 1;
|
||||
return LatLng(
|
||||
streetResult.latitude + (hnInt - 1) * 0.00015,
|
||||
streetResult.longitude + ((hnInt / 25).floor() % 2 == 0 ? 0 : 0.0002),
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Default to Hoogerheide center
|
||||
return const LatLng(51.4243390, 4.3238380);
|
||||
}
|
||||
|
||||
LatLng? _lookupInStreets(String street) {
|
||||
final ns = _normalize(street);
|
||||
|
||||
// Exact match
|
||||
for (final entry in HoogerheideStreets.streetCoords.entries) {
|
||||
if (_normalize(entry.key) == ns) {
|
||||
return LatLng(entry.value[0], entry.value[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Partial match
|
||||
for (final entry in HoogerheideStreets.streetCoords.entries) {
|
||||
final ek = _normalize(entry.key);
|
||||
if (ns.contains(ek) || ek.contains(ns)) {
|
||||
return LatLng(entry.value[0], entry.value[1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Batch-geocode a list of stops, updating coords only when they changed.
|
||||
Future<List<LatLng?>> geocodeStops(List<(String street, String houseNum)> addresses) async {
|
||||
final results = <LatLng?>[];
|
||||
for (final (street, houseNum) in addresses) {
|
||||
try {
|
||||
results.add(await geocode(street, houseNum));
|
||||
} catch (_) {
|
||||
results.add(null);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import 'dart:io';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import '../models/stop.dart';
|
||||
|
||||
class GpxExportService {
|
||||
/// Export route stops as GPX file with waypoints and optional track.
|
||||
/// Returns the file path of the saved GPX file.
|
||||
static Future<String> exportRoute({
|
||||
required String routeName,
|
||||
required List<Stop> stops,
|
||||
List<LatLng>? trackPoints,
|
||||
}) async {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
buffer.writeln('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
buffer.writeln(
|
||||
'<gpx version="1.1" creator="Delivery Route App" xmlns="http://www.topografix.com/GPX/1/1">');
|
||||
buffer.writeln(' <metadata>');
|
||||
buffer.writeln(' <name>${_escapeXml(routeName)}</name>');
|
||||
buffer.writeln(' <time>${DateTime.now().toUtc().toIso8601String()}</time>');
|
||||
buffer.writeln(' </metadata>');
|
||||
|
||||
// Waypoints
|
||||
for (final stop in stops) {
|
||||
buffer.writeln(' <wpt lat="${stop.lat}" lon="${stop.lng}">');
|
||||
buffer.writeln(' <name>${_escapeXml(stop.street)} ${_escapeXml(stop.houseNumber)}</name>');
|
||||
if (stop.newspapers.isNotEmpty) {
|
||||
buffer.writeln(' <desc>${_escapeXml(stop.newspapers.join(', '))}</desc>');
|
||||
}
|
||||
if (stop.notes.isNotEmpty) {
|
||||
buffer.writeln(' <cmt>${_escapeXml(stop.notes)}</cmt>');
|
||||
}
|
||||
buffer.writeln(' </wpt>');
|
||||
}
|
||||
|
||||
// Track (if route points available)
|
||||
if (trackPoints != null && trackPoints.isNotEmpty) {
|
||||
buffer.writeln(' <trk>');
|
||||
buffer.writeln(' <name>${_escapeXml(routeName)} - Route</name>');
|
||||
buffer.writeln(' <trkseg>');
|
||||
for (final point in trackPoints) {
|
||||
buffer.writeln(
|
||||
' <trkpt lat="${point.latitude}" lon="${point.longitude}"></trkpt>');
|
||||
}
|
||||
buffer.writeln(' </trkseg>');
|
||||
buffer.writeln(' </trk>');
|
||||
}
|
||||
|
||||
buffer.writeln('</gpx>');
|
||||
|
||||
// Save to file
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final sanitized = routeName.replaceAll(RegExp(r'[^\w\s-]'), '').replaceAll(' ', '_');
|
||||
final fileName = 'route_${sanitized}_${_dateStamp()}.gpx';
|
||||
final file = File('${directory.path}/$fileName');
|
||||
await file.writeAsString(buffer.toString());
|
||||
|
||||
return file.path;
|
||||
}
|
||||
|
||||
static String _escapeXml(String text) {
|
||||
return text
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
static String _dateStamp() {
|
||||
final now = DateTime.now();
|
||||
return '${now.year}${now.month.toString().padLeft(2, '0')}${now.day.toString().padLeft(2, '0')}_${now.hour.toString().padLeft(2, '0')}${now.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../models/stop.dart';
|
||||
|
||||
class RoutingService {
|
||||
static final RoutingService _instance = RoutingService._internal();
|
||||
factory RoutingService() => _instance;
|
||||
RoutingService._internal();
|
||||
|
||||
static const _osrmBaseUrl = 'https://router.project-osrm.org';
|
||||
|
||||
/// Fetch driving route through all stops via OSRM.
|
||||
/// Returns list of polyline points, or null on failure.
|
||||
Future<List<LatLng>?> fetchRoute(List<Stop> stops) async {
|
||||
if (stops.length < 2) return null;
|
||||
|
||||
final coords = stops.map((s) => '${s.lng},${s.lat}').join(';');
|
||||
final url = '$_osrmBaseUrl/route/v1/driving/$coords?overview=full&geometries=polyline';
|
||||
|
||||
try {
|
||||
final resp = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10));
|
||||
if (resp.statusCode == 200) {
|
||||
final data = jsonDecode(resp.body);
|
||||
if (data['code'] == 'Ok') {
|
||||
final routes = data['routes'] as List;
|
||||
if (routes.isNotEmpty) {
|
||||
final geometry = routes[0]['geometry'] as String;
|
||||
return decodePolyline(geometry);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// Network error — caller should fall back
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Optimize stop order using OSRM Trip (TSP solver).
|
||||
/// Returns optimized stops list, or null on failure.
|
||||
Future<List<Stop>?> optimizeRoute(List<Stop> stops) async {
|
||||
if (stops.length < 2) return null;
|
||||
|
||||
final coords = stops.map((s) => '${s.lng},${s.lat}').join(';');
|
||||
final url = '$_osrmBaseUrl/trip/v1/driving/$coords?overview=full&geometries=polyline';
|
||||
|
||||
try {
|
||||
final resp = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10));
|
||||
if (resp.statusCode == 200) {
|
||||
final data = jsonDecode(resp.body);
|
||||
if (data['code'] == 'Ok') {
|
||||
final waypoints = data['waypoints'] as List;
|
||||
final optimizedOrder = waypoints.map((w) => w['waypoint_index'] as int).toList();
|
||||
|
||||
final optimizedStops = <Stop>[];
|
||||
for (var i = 0; i < optimizedOrder.length; i++) {
|
||||
final stop = stops[optimizedOrder[i]];
|
||||
optimizedStops.add(stop.copyWith(sequence: i));
|
||||
}
|
||||
return optimizedStops;
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// Network error — caller should fall back
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Nearest-neighbor TSP heuristic as local fallback.
|
||||
static List<Stop> optimizeLocally(List<Stop> route) {
|
||||
if (route.length <= 2) return route;
|
||||
|
||||
final opt = <Stop>[];
|
||||
final rem = [...route];
|
||||
var cur = rem.removeAt(0);
|
||||
opt.add(cur);
|
||||
|
||||
while (rem.isNotEmpty) {
|
||||
var nearestIdx = 0;
|
||||
var nearestDist = double.infinity;
|
||||
for (var i = 0; i < rem.length; i++) {
|
||||
final d = haversine(cur.lat, cur.lng, rem[i].lat, rem[i].lng);
|
||||
if (d < nearestDist) {
|
||||
nearestDist = d;
|
||||
nearestIdx = i;
|
||||
}
|
||||
}
|
||||
cur = rem.removeAt(nearestIdx);
|
||||
opt.add(cur);
|
||||
}
|
||||
|
||||
return opt.asMap().entries.map((e) => e.value.copyWith(sequence: e.key)).toList();
|
||||
}
|
||||
|
||||
/// Haversine distance in kilometers using dart:math.
|
||||
static double haversine(double lat1, double lng1, double lat2, double lng2) {
|
||||
const earthRadius = 6371.0; // km
|
||||
final dLat = _degToRad(lat2 - lat1);
|
||||
final dLng = _degToRad(lng2 - lng1);
|
||||
final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
||||
math.cos(_degToRad(lat1)) * math.cos(_degToRad(lat2)) *
|
||||
math.sin(dLng / 2) * math.sin(dLng / 2);
|
||||
final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
|
||||
return earthRadius * c;
|
||||
}
|
||||
|
||||
static double _degToRad(double deg) => deg * math.pi / 180.0;
|
||||
|
||||
/// Decode OSRM-encoded polyline string to list of LatLng.
|
||||
static List<LatLng> decodePolyline(String encoded) {
|
||||
final points = <LatLng>[];
|
||||
var index = 0;
|
||||
final len = encoded.length;
|
||||
var lat = 0;
|
||||
var lng = 0;
|
||||
|
||||
while (index < len) {
|
||||
var b = 0;
|
||||
var shift = 0;
|
||||
var result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
final dlat = (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
|
||||
lat += dlat;
|
||||
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
final dlng = (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
|
||||
lng += dlng;
|
||||
|
||||
points.add(LatLng(lat / 1e6, lng / 1e6));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
flutter/ephemeral
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "delivery_app")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.example.delivery_app")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Define build configuration options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add preprocessor definitions for the application ID.
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Called when first Flutter frame received.
|
||||
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
|
||||
}
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "delivery_app");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "delivery_app");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(
|
||||
project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
GdkRGBA background_color;
|
||||
// Background defaults to black, override it here if necessary, e.g. #00000000
|
||||
// for transparent.
|
||||
gdk_rgba_parse(&background_color, "#000000");
|
||||
fl_view_set_background_color(view, &background_color);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
// Show the window when Flutter renders.
|
||||
// Requires the view to be realized so we can start rendering.
|
||||
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||
self);
|
||||
gtk_widget_realize(GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application,
|
||||
gchar*** arguments,
|
||||
int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line =
|
||||
my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
// the application to be recognized beyond its binary name.
|
||||
g_set_prgname(APPLICATION_ID);
|
||||
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID, "flags",
|
||||
G_APPLICATION_NON_UNIQUE, nullptr));
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication,
|
||||
my_application,
|
||||
MY,
|
||||
APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import geolocator_apple
|
||||
import sqflite_darwin
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
}
|
||||
|
|
@ -0,0 +1,705 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||
buildPhases = (
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Flutter Assemble";
|
||||
productName = FLX;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||
remoteInfo = FLX;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Bundle Framework";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* delivery_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "delivery_app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||
);
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10E42044A3C60003C045 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33FAB671232836740065AC1E /* Runner */,
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10ED2044A3C60003C045 /* delivery_app.app */,
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC11242044D66E0003C045 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||
);
|
||||
name = Resources;
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||
);
|
||||
path = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33FAB671232836740065AC1E /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||
33E51914231749380026EE4D /* Release.entitlements */,
|
||||
33CC11242044D66E0003C045 /* Resources */,
|
||||
33BA886A226E78AF003329D5 /* Configs */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* delivery_app.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 33CC10EC2044A3C60003C045;
|
||||
};
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
33CC111A2044C6BA0003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
33CC10EC2044A3C60003C045 /* Runner */,
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */,
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C80D3294CF70F00263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||
);
|
||||
inputPaths = (
|
||||
Flutter/ephemeral/tripwire,
|
||||
);
|
||||
outputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C80D1294CF70F00263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC10EC2044A3C60003C045 /* Runner */;
|
||||
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
33CC10F52044A3C60003C045 /* Base */,
|
||||
);
|
||||
name = MainMenu.xib;
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/delivery_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/delivery_app";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/delivery_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/delivery_app";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.deliveryApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/delivery_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/delivery_app";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C80DB294CF71000263BE5 /* Debug */,
|
||||
331C80DC294CF71000263BE5 /* Release */,
|
||||
331C80DD294CF71000263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10F92044A3C60003C045 /* Debug */,
|
||||
33CC10FA2044A3C60003C045 /* Release */,
|
||||
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10FC2044A3C60003C045 /* Debug */,
|
||||
33CC10FD2044A3C60003C045 /* Release */,
|
||||
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC111C2044C6BA0003C045 /* Debug */,
|
||||
33CC111D2044C6BA0003C045 /* Release */,
|
||||
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "delivery_app.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "delivery_app.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "delivery_app.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "delivery_app.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||