Add all project files

This commit is contained in:
OpenClaw Bot 2026-05-01 13:03:14 +00:00
parent a62f60ed50
commit 186c88ce21
145 changed files with 8627 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@ -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

45
.metadata Normal file
View File

@ -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'

156
CHANGELOG.md Normal file
View File

@ -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

28
analysis_options.yaml Normal file
View File

@ -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

14
android/.gitignore vendored Normal file
View File

@ -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

View File

@ -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 = "../.."
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
package com.example.delivery_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

24
android/build.gradle.kts Normal file
View File

@ -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)
}

View File

@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

View File

@ -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

View File

@ -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")

File diff suppressed because one or more lines are too long

View File

@ -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);
}
}

34
ios/.gitignore vendored Normal file
View File

@ -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

View File

@ -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>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -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.

View File

@ -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>

View File

@ -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>

70
ios/Runner/Info.plist Normal file
View File

@ -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>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,6 @@
import Flutter
import UIKit
class SceneDelegate: FlutterSceneDelegate {
}

View File

@ -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.
}
}

View File

@ -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],
};
}

28
lib/main.dart Normal file
View File

@ -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(),
);
}
}

View File

@ -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,
});
}

82
lib/models/stop.dart Normal file
View File

@ -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(",")})';
}

315
lib/pages/builder_page.dart Normal file
View File

@ -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});
}

895
lib/pages/route_page.dart Normal file
View File

@ -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));
}
}

233
lib/pages/routes_page.dart Normal file
View File

@ -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;
}
}
}

View File

@ -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,
);
}
}

View File

@ -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;
}
}
}

View File

@ -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),
),
);
}

View File

@ -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();
}
}
}

351
lib/services/database.dart Normal file
View File

@ -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;
}
}
}

117
lib/services/geocoding.dart Normal file
View File

@ -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;
}
}

View File

@ -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('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
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')}';
}
}

144
lib/services/routing.dart Normal file
View File

@ -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;
}
}

1
linux/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
flutter/ephemeral

128
linux/CMakeLists.txt Normal file
View File

@ -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()

View File

@ -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}
)

View File

@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}

View File

@ -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_

View File

@ -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)

View File

@ -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}")

6
linux/runner/main.cc Normal file
View File

@ -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);
}

View File

@ -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));
}

View File

@ -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_

7
macos/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View File

@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -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"))
}

View File

@ -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 */;
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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
}
}

Some files were not shown because too many files have changed in this diff Show More