Remove Deprecated bus terminal
Build and Push Docker Images / detect-changes (push) Successful in 10s
Build and Push Docker Images / build-auth-api (push) Successful in 58s
Build and Push Docker Images / build-employee-api (push) Successful in 57s
Build and Push Docker Images / build-transit-api (push) Successful in 57s
Build and Push Docker Images / build-error-log-api (push) Successful in 59s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m11s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
@@ -1,44 +0,0 @@
|
|||||||
# Flutter / Dart
|
|
||||||
.dart_tool/
|
|
||||||
.flutter-plugins
|
|
||||||
.flutter-plugins-dependencies
|
|
||||||
.pub-cache/
|
|
||||||
.pub/
|
|
||||||
/build/
|
|
||||||
/coverage/
|
|
||||||
**/doc/api/
|
|
||||||
**/ios/Flutter/.last_build_id
|
|
||||||
|
|
||||||
# IDE / editor
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# OS files
|
|
||||||
.DS_Store
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
# Environment and secrets
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Generated and platform artifacts
|
|
||||||
android/key.properties
|
|
||||||
android/app/debug
|
|
||||||
android/app/profile
|
|
||||||
android/app/release
|
|
||||||
|
|
||||||
# Logs and temp files
|
|
||||||
*.log
|
|
||||||
*.tmp
|
|
||||||
|
|
||||||
# Symbolication / obfuscation
|
|
||||||
*.symbols
|
|
||||||
*.map.json
|
|
||||||
|
|
||||||
# Legacy migration note
|
|
||||||
legacy_windows_xp_plan.md
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# 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: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694"
|
|
||||||
channel: "stable"
|
|
||||||
|
|
||||||
project_type: app
|
|
||||||
|
|
||||||
# Tracks metadata for the flutter migrate command
|
|
||||||
migration:
|
|
||||||
platforms:
|
|
||||||
- platform: root
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
- platform: android
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
- platform: ios
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
- platform: linux
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
- platform: macos
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
- platform: web
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
- platform: windows
|
|
||||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
|
||||||
|
|
||||||
# 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'
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
analyzer:
|
|
||||||
errors:
|
|
||||||
deprecated_member_use: ignore
|
|
||||||
use_build_context_synchronously: ignore
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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.bus_terminal"
|
|
||||||
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.bus_terminal"
|
|
||||||
// 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 = "../.."
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:label="bus_terminal"
|
|
||||||
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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.example.bus_terminal
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,18 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
|
||||||
android.useAndroidX=true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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")
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
description: This file stores settings for Dart & Flutter DevTools.
|
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
|
||||||
extensions:
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
**/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
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "Generated.xcconfig"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "Generated.xcconfig"
|
|
||||||
@@ -1,620 +0,0 @@
|
|||||||
// !$*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.busTerminal;
|
|
||||||
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.busTerminal.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.busTerminal.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.busTerminal.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.busTerminal;
|
|
||||||
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.busTerminal;
|
|
||||||
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 */;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "self:">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "group:Runner.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@@ -1,5 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?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>Bus Terminal</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>bus_terminal</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>
|
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string>We need your location to show the bus route on the map and navigate you through your scheduled stops.</string>
|
|
||||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
||||||
<string>We need your location to show the bus route on the map and navigate you through your scheduled stops.</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#import "GeneratedPluginRegistrant.h"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import Flutter
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
class SceneDelegate: FlutterSceneDelegate {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'screens/login_screen.dart';
|
|
||||||
import 'screens/schedule_overview_screen.dart';
|
|
||||||
import 'services/auth_service.dart';
|
|
||||||
import 'theme/app_theme.dart';
|
|
||||||
import 'services/app_navigator.dart';
|
|
||||||
import 'services/error_log_service.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
FlutterError.onError = (details) {
|
|
||||||
FlutterError.presentError(details);
|
|
||||||
void report() => ErrorLogService.report(
|
|
||||||
service: 'deprecated',
|
|
||||||
partOfService: 'FlutterError.onError',
|
|
||||||
error: details.exceptionAsString(),
|
|
||||||
);
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
report();
|
|
||||||
};
|
|
||||||
|
|
||||||
PlatformDispatcher.instance.onError = (error, stack) {
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
ErrorLogService.report(
|
|
||||||
service: 'deprecated',
|
|
||||||
partOfService: 'PlatformDispatcher.onError',
|
|
||||||
error: '$error\n$stack',
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
runZonedGuarded(
|
|
||||||
() {
|
|
||||||
runApp(const MainApp());
|
|
||||||
},
|
|
||||||
(error, stack) {
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
ErrorLogService.report(
|
|
||||||
service: 'deprecated',
|
|
||||||
partOfService: 'runZonedGuarded',
|
|
||||||
error: '$error\n$stack',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MainApp extends StatelessWidget {
|
|
||||||
const MainApp({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MaterialApp(
|
|
||||||
title: 'Bus Terminal',
|
|
||||||
navigatorKey: navigatorKey,
|
|
||||||
scaffoldMessengerKey: messengerKey,
|
|
||||||
theme: AppTheme.lightTheme(),
|
|
||||||
darkTheme: AppTheme.darkTheme(),
|
|
||||||
themeMode: ThemeMode.system,
|
|
||||||
routes: {
|
|
||||||
'/login': (context) => const LoginScreen(),
|
|
||||||
'/schedule': (context) => const ScheduleOverviewScreen(),
|
|
||||||
},
|
|
||||||
home: const _InitialRoute(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initial route checks if user is logged in
|
|
||||||
class _InitialRoute extends StatelessWidget {
|
|
||||||
const _InitialRoute();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return FutureBuilder<bool>(
|
|
||||||
future: AuthService().isLoggedIn(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Scaffold(
|
|
||||||
body: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If logged in, go to schedule; otherwise go to login
|
|
||||||
return snapshot.data == true
|
|
||||||
? const ScheduleOverviewScreen()
|
|
||||||
: const LoginScreen();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
/// Navigation route point
|
|
||||||
class NavigationPoint {
|
|
||||||
final double latitude;
|
|
||||||
final double longitude;
|
|
||||||
final String? name;
|
|
||||||
|
|
||||||
NavigationPoint({
|
|
||||||
required this.latitude,
|
|
||||||
required this.longitude,
|
|
||||||
this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
LatLng toLatLng() => LatLng(latitude, longitude);
|
|
||||||
|
|
||||||
factory NavigationPoint.fromLatLng(LatLng latlng, {String? name}) {
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: latlng.latitude,
|
|
||||||
longitude: latlng.longitude,
|
|
||||||
name: name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'latitude': latitude,
|
|
||||||
'longitude': longitude,
|
|
||||||
'name': name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory NavigationPoint.fromJson(Map<String, dynamic> json) {
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: json['latitude'] as double,
|
|
||||||
longitude: json['longitude'] as double,
|
|
||||||
name: json['name'] as String?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Route leg with distance and duration
|
|
||||||
class RouteLeg {
|
|
||||||
final List<NavigationPoint> points;
|
|
||||||
final double distance; // in meters
|
|
||||||
final int duration; // in seconds
|
|
||||||
final String summary;
|
|
||||||
|
|
||||||
RouteLeg({
|
|
||||||
required this.points,
|
|
||||||
required this.distance,
|
|
||||||
required this.duration,
|
|
||||||
required this.summary,
|
|
||||||
});
|
|
||||||
|
|
||||||
String getDistanceKm() => (distance / 1000).toStringAsFixed(1);
|
|
||||||
String getDurationMinutes() => (duration ~/ 60).toString();
|
|
||||||
|
|
||||||
factory RouteLeg.fromCoordinates(
|
|
||||||
List<dynamic> coordinates, {
|
|
||||||
double distance = 0.0,
|
|
||||||
int duration = 0,
|
|
||||||
String summary = 'Route',
|
|
||||||
}) {
|
|
||||||
return RouteLeg(
|
|
||||||
points: coordinates
|
|
||||||
.map((coord) {
|
|
||||||
if (coord is List && coord.length >= 2) {
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: (coord[1] as num).toDouble(),
|
|
||||||
longitude: (coord[0] as num).toDouble(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.whereType<NavigationPoint>()
|
|
||||||
.toList(),
|
|
||||||
distance: distance,
|
|
||||||
duration: duration,
|
|
||||||
summary: summary,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory RouteLeg.fromPoints({
|
|
||||||
required List<NavigationPoint> points,
|
|
||||||
required double distance,
|
|
||||||
required int duration,
|
|
||||||
String summary = 'Route',
|
|
||||||
}) {
|
|
||||||
return RouteLeg(
|
|
||||||
points: points,
|
|
||||||
distance: distance,
|
|
||||||
duration: duration,
|
|
||||||
summary: summary,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory RouteLeg.fromJson(Map<String, dynamic> json) {
|
|
||||||
final geometry = json['geometry'] as Map<String, dynamic>?;
|
|
||||||
final coordinates = geometry?['coordinates'] as List? ?? [];
|
|
||||||
|
|
||||||
return RouteLeg(
|
|
||||||
points: coordinates
|
|
||||||
.map((coord) {
|
|
||||||
if (coord is List && coord.length >= 2) {
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: (coord[1] as num).toDouble(),
|
|
||||||
longitude: (coord[0] as num).toDouble(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.whereType<NavigationPoint>()
|
|
||||||
.toList(),
|
|
||||||
distance: (json['distance'] as num?)?.toDouble() ?? 0.0,
|
|
||||||
duration: (json['duration'] as num?)?.toInt() ?? 0,
|
|
||||||
summary: (json['summary'] as String?) ?? 'Route',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Complete navigation route from start to end
|
|
||||||
class NavigationRoute {
|
|
||||||
final NavigationPoint start;
|
|
||||||
final NavigationPoint end;
|
|
||||||
final List<RouteLeg> legs;
|
|
||||||
final double totalDistance; // in meters
|
|
||||||
final int totalDuration; // in seconds
|
|
||||||
|
|
||||||
NavigationRoute({
|
|
||||||
required this.start,
|
|
||||||
required this.end,
|
|
||||||
required this.legs,
|
|
||||||
required this.totalDistance,
|
|
||||||
required this.totalDuration,
|
|
||||||
});
|
|
||||||
|
|
||||||
String getTotalDistanceKm() => (totalDistance / 1000).toStringAsFixed(1);
|
|
||||||
String getTotalDurationMinutes() => (totalDuration ~/ 60).toString();
|
|
||||||
String getTotalDurationHM() {
|
|
||||||
final hours = totalDuration ~/ 3600;
|
|
||||||
final minutes = (totalDuration % 3600) ~/ 60;
|
|
||||||
if (hours > 0) {
|
|
||||||
return '${hours}h ${minutes}m';
|
|
||||||
}
|
|
||||||
return '${minutes}m';
|
|
||||||
}
|
|
||||||
|
|
||||||
List<NavigationPoint> getAllPoints() {
|
|
||||||
final allPoints = <NavigationPoint>[];
|
|
||||||
for (final leg in legs) {
|
|
||||||
allPoints.addAll(leg.points);
|
|
||||||
}
|
|
||||||
return allPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory NavigationRoute.fromJson(Map<String, dynamic> json) {
|
|
||||||
final routes = json['routes'] as List?;
|
|
||||||
if (routes == null || routes.isEmpty) {
|
|
||||||
throw Exception('No route found in response');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routeData = routes[0] as Map<String, dynamic>?;
|
|
||||||
if (routeData == null) {
|
|
||||||
throw Exception('Invalid route data');
|
|
||||||
}
|
|
||||||
|
|
||||||
return NavigationRoute.fromOsrmRouteData(
|
|
||||||
routeData,
|
|
||||||
start: NavigationPoint(latitude: 0, longitude: 0),
|
|
||||||
end: NavigationPoint(latitude: 0, longitude: 0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory NavigationRoute.fromOsrmRouteData(
|
|
||||||
Map<String, dynamic> routeData, {
|
|
||||||
required NavigationPoint start,
|
|
||||||
required NavigationPoint end,
|
|
||||||
}) {
|
|
||||||
final geometry = routeData['geometry'] as Map<String, dynamic>?;
|
|
||||||
final coordinates = geometry?['coordinates'] as List? ?? [];
|
|
||||||
final distance = (routeData['distance'] as num?)?.toDouble() ?? 0.0;
|
|
||||||
final duration = (routeData['duration'] as num?)?.toInt() ?? 0;
|
|
||||||
|
|
||||||
final leg = RouteLeg.fromCoordinates(
|
|
||||||
coordinates,
|
|
||||||
distance: distance,
|
|
||||||
duration: duration,
|
|
||||||
summary: (routeData['summary'] as String?) ?? 'Route',
|
|
||||||
);
|
|
||||||
|
|
||||||
return NavigationRoute(
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
legs: [leg],
|
|
||||||
totalDistance: distance,
|
|
||||||
totalDuration: duration,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result of an optimized trip calculation with reordered stops.
|
|
||||||
class OptimizedRouteResult {
|
|
||||||
final NavigationRoute route;
|
|
||||||
final List<NavigationPoint> orderedStops;
|
|
||||||
|
|
||||||
OptimizedRouteResult({
|
|
||||||
required this.route,
|
|
||||||
required this.orderedStops,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
/// Bus Line model
|
|
||||||
class BusLine {
|
|
||||||
final int id;
|
|
||||||
final int lineNumber;
|
|
||||||
final String startStop;
|
|
||||||
final String endStop;
|
|
||||||
final int estimatedDuration; // in minutes
|
|
||||||
final String? description;
|
|
||||||
|
|
||||||
BusLine({
|
|
||||||
required this.id,
|
|
||||||
required this.lineNumber,
|
|
||||||
required this.startStop,
|
|
||||||
required this.endStop,
|
|
||||||
required this.estimatedDuration,
|
|
||||||
this.description,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory BusLine.fromJson(Map<String, dynamic> json) {
|
|
||||||
return BusLine(
|
|
||||||
id: json['id'] as int,
|
|
||||||
lineNumber: json['lineNumber'] as int,
|
|
||||||
startStop: json['startStop'] as String,
|
|
||||||
endStop: json['endStop'] as String,
|
|
||||||
estimatedDuration: json['estimatedDuration'] as int,
|
|
||||||
description: json['description'] as String?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'lineNumber': lineNumber,
|
|
||||||
'startStop': startStop,
|
|
||||||
'endStop': endStop,
|
|
||||||
'estimatedDuration': estimatedDuration,
|
|
||||||
'description': description,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bus type model
|
|
||||||
class BusType {
|
|
||||||
final int id;
|
|
||||||
final String name;
|
|
||||||
final int seatCapacity;
|
|
||||||
final String licensePlate;
|
|
||||||
|
|
||||||
BusType({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.seatCapacity,
|
|
||||||
required this.licensePlate,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory BusType.fromJson(Map<String, dynamic> json) {
|
|
||||||
return BusType(
|
|
||||||
id: json['id'] as int,
|
|
||||||
name: json['name'] as String,
|
|
||||||
seatCapacity: json['seatCapacity'] as int,
|
|
||||||
licensePlate: json['licensePlate'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'name': name,
|
|
||||||
'seatCapacity': seatCapacity,
|
|
||||||
'licensePlate': licensePlate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Route model for daily schedule
|
|
||||||
class Route {
|
|
||||||
final int id;
|
|
||||||
final BusLine busLine;
|
|
||||||
final BusType busType;
|
|
||||||
final DateTime startTime;
|
|
||||||
final DateTime? endTime;
|
|
||||||
final String status; // scheduled, in_progress, completed, cancelled
|
|
||||||
|
|
||||||
Route({
|
|
||||||
required this.id,
|
|
||||||
required this.busLine,
|
|
||||||
required this.busType,
|
|
||||||
required this.startTime,
|
|
||||||
this.endTime,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Route.fromJson(Map<String, dynamic> json) {
|
|
||||||
return Route(
|
|
||||||
id: json['id'] as int,
|
|
||||||
busLine: BusLine.fromJson(json['busLine'] as Map<String, dynamic>),
|
|
||||||
busType: BusType.fromJson(json['busType'] as Map<String, dynamic>),
|
|
||||||
startTime: DateTime.parse(json['startTime'] as String),
|
|
||||||
endTime:
|
|
||||||
json['endTime'] != null ? DateTime.parse(json['endTime'] as String) : null,
|
|
||||||
status: json['status'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'busLine': busLine.toJson(),
|
|
||||||
'busType': busType.toJson(),
|
|
||||||
'startTime': startTime.toIso8601String(),
|
|
||||||
'endTime': endTime?.toIso8601String(),
|
|
||||||
'status': status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Daily schedule model
|
|
||||||
class DailySchedule {
|
|
||||||
final List<Route> routes;
|
|
||||||
final DateTime date;
|
|
||||||
|
|
||||||
DailySchedule({
|
|
||||||
required this.routes,
|
|
||||||
required this.date,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory DailySchedule.fromJson(Map<String, dynamic> json) {
|
|
||||||
return DailySchedule(
|
|
||||||
routes: (json['routes'] as List)
|
|
||||||
.map((r) => Route.fromJson(r as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
date: DateTime.parse(json['date'] as String),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'routes': routes.map((r) => r.toJson()).toList(),
|
|
||||||
'date': date.toIso8601String(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop model for the new schedule system.
|
|
||||||
class ScheduleStop {
|
|
||||||
final int id;
|
|
||||||
final int order;
|
|
||||||
final String name;
|
|
||||||
final double latitude;
|
|
||||||
final double longitude;
|
|
||||||
final int estimatedArrivalMinutes;
|
|
||||||
|
|
||||||
ScheduleStop({
|
|
||||||
required this.id,
|
|
||||||
required this.order,
|
|
||||||
required this.name,
|
|
||||||
required this.latitude,
|
|
||||||
required this.longitude,
|
|
||||||
required this.estimatedArrivalMinutes,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ScheduleStop.fromJson(Map<String, dynamic> json) {
|
|
||||||
return ScheduleStop(
|
|
||||||
id: (json['id'] as num).toInt(),
|
|
||||||
order: (json['order'] as num).toInt(),
|
|
||||||
name: (json['name'] ?? json['stopName'] ?? '') as String,
|
|
||||||
latitude: (json['latitude'] as num).toDouble(),
|
|
||||||
longitude: (json['longitude'] as num).toDouble(),
|
|
||||||
estimatedArrivalMinutes: (json['estimatedArrivalMinutes'] as num?)?.toInt() ?? 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedule model for the new public schedule API.
|
|
||||||
class ServiceSchedule {
|
|
||||||
final int id;
|
|
||||||
final int busLineId;
|
|
||||||
final int busId;
|
|
||||||
final DateTime startTime;
|
|
||||||
final DateTime endTime;
|
|
||||||
final String status;
|
|
||||||
final int lineNumber;
|
|
||||||
final String busName;
|
|
||||||
final List<int> weekdays;
|
|
||||||
|
|
||||||
ServiceSchedule({
|
|
||||||
required this.id,
|
|
||||||
required this.busLineId,
|
|
||||||
required this.busId,
|
|
||||||
required this.startTime,
|
|
||||||
required this.endTime,
|
|
||||||
required this.status,
|
|
||||||
required this.lineNumber,
|
|
||||||
required this.busName,
|
|
||||||
required this.weekdays,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ServiceSchedule.fromJson(Map<String, dynamic> json) {
|
|
||||||
final rawWeekdays = json['weekdays'];
|
|
||||||
final weekdays = rawWeekdays is List
|
|
||||||
? rawWeekdays.map((day) => (day as num).toInt()).toList()
|
|
||||||
: rawWeekdays is String
|
|
||||||
? (jsonDecode(rawWeekdays) as List).map((day) => (day as num).toInt()).toList()
|
|
||||||
: <int>[];
|
|
||||||
|
|
||||||
return ServiceSchedule(
|
|
||||||
id: (json['id'] as num).toInt(),
|
|
||||||
busLineId: (json['busLineId'] as num).toInt(),
|
|
||||||
busId: (json['busId'] as num).toInt(),
|
|
||||||
startTime: DateTime.parse(json['startTime'] as String),
|
|
||||||
endTime: DateTime.parse(json['endTime'] as String),
|
|
||||||
status: (json['status'] ?? 'planned') as String,
|
|
||||||
lineNumber: (json['lineNumber'] as num).toInt(),
|
|
||||||
busName: (json['busName'] ?? '') as String,
|
|
||||||
weekdays: weekdays,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isRecurring => weekdays.isNotEmpty;
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/// User model
|
|
||||||
class User {
|
|
||||||
final int id;
|
|
||||||
final int userCode;
|
|
||||||
final String? job;
|
|
||||||
|
|
||||||
User({
|
|
||||||
required this.id,
|
|
||||||
required this.userCode,
|
|
||||||
this.job,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory User.fromJson(Map<String, dynamic> json) {
|
|
||||||
// userCode may be provided as 'userCode' or 'usercode' and may be int or string
|
|
||||||
final dynamic rawUserCode = json['userCode'] ?? json['usercode'] ?? json['user_code'];
|
|
||||||
int parsedUserCode;
|
|
||||||
if (rawUserCode is int) {
|
|
||||||
parsedUserCode = rawUserCode;
|
|
||||||
} else if (rawUserCode is String) {
|
|
||||||
parsedUserCode = int.tryParse(rawUserCode) ?? (throw FormatException('Invalid userCode'));
|
|
||||||
} else {
|
|
||||||
// If the backend doesn't return a userCode, treat as unknown (-1)
|
|
||||||
parsedUserCode = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return User(
|
|
||||||
id: json['id'] as int,
|
|
||||||
userCode: parsedUserCode,
|
|
||||||
job: json['job'] as String?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'userCode': userCode,
|
|
||||||
'job': job,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Login response model
|
|
||||||
class AuthResponse {
|
|
||||||
final String token;
|
|
||||||
final User? user;
|
|
||||||
|
|
||||||
AuthResponse({
|
|
||||||
required this.token,
|
|
||||||
this.user,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
|
||||||
final dynamic rawToken = json['token'] ?? json['Token'] ?? json['authToken'];
|
|
||||||
final token = rawToken?.toString();
|
|
||||||
if (token == null || token.isEmpty) throw FormatException('Missing token in auth response');
|
|
||||||
|
|
||||||
// Determine if a user object exists and appears valid
|
|
||||||
Map<String, dynamic>? userMap;
|
|
||||||
if (json.containsKey('user') && json['user'] is Map<String, dynamic>) {
|
|
||||||
userMap = json['user'] as Map<String, dynamic>;
|
|
||||||
} else {
|
|
||||||
// maybe user fields are top-level (id, userCode, job)
|
|
||||||
final hasUserFields = json.containsKey('id') || json.containsKey('userCode') || json.containsKey('usercode') || json.containsKey('user_code');
|
|
||||||
if (hasUserFields) {
|
|
||||||
// treat whole json as user map
|
|
||||||
userMap = json.cast<String, dynamic>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
User? parsedUser;
|
|
||||||
if (userMap != null) {
|
|
||||||
try {
|
|
||||||
parsedUser = User.fromJson(userMap);
|
|
||||||
} catch (e) {
|
|
||||||
// If parsing user fails, ignore user but keep token
|
|
||||||
parsedUser = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return AuthResponse(
|
|
||||||
token: token,
|
|
||||||
user: parsedUser,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../models/schedule.dart' as schedule;
|
|
||||||
import '../services/schedule_service.dart';
|
|
||||||
import '../services/auth_service.dart';
|
|
||||||
import '../services/exceptions.dart';
|
|
||||||
import '../widgets/route_card.dart';
|
|
||||||
|
|
||||||
class DailyScheduleScreen extends StatefulWidget {
|
|
||||||
const DailyScheduleScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<DailyScheduleScreen> createState() => _DailyScheduleScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DailyScheduleScreenState extends State<DailyScheduleScreen> {
|
|
||||||
final _scheduleService = ScheduleService();
|
|
||||||
final _authService = AuthService();
|
|
||||||
late Future<schedule.DailySchedule> _scheduleFuture;
|
|
||||||
DateTime _selectedDate = DateTime.now();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_scheduleFuture = _scheduleService.getTodaySchedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleLogout() async {
|
|
||||||
await _authService.logout();
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).pushReplacementNamed('/login');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refreshSchedule() async {
|
|
||||||
setState(() {
|
|
||||||
_scheduleFuture = _scheduleService.getDailySchedule(_selectedDate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _selectDate() async {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
final picked = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: _selectedDate,
|
|
||||||
firstDate: DateTime.now().subtract(const Duration(days: 7)),
|
|
||||||
lastDate: DateTime.now().add(const Duration(days: 30)),
|
|
||||||
builder: (context, child) {
|
|
||||||
return Theme(
|
|
||||||
data: Theme.of(context).copyWith(
|
|
||||||
dialogBackgroundColor: colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: child!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (picked != null && picked != _selectedDate) {
|
|
||||||
setState(() {
|
|
||||||
_selectedDate = picked;
|
|
||||||
});
|
|
||||||
_refreshSchedule();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group routes by hour for better visual organization
|
|
||||||
Map<int, List<schedule.Route>> _groupByHour(List<schedule.Route> routes) {
|
|
||||||
final grouped = <int, List<schedule.Route>>{};
|
|
||||||
for (final route in routes) {
|
|
||||||
final hour = route.startTime.hour;
|
|
||||||
grouped.putIfAbsent(hour, () => []).add(route);
|
|
||||||
}
|
|
||||||
return grouped;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _moveDay(int deltaDays) {
|
|
||||||
setState(() {
|
|
||||||
_selectedDate = _selectedDate.add(Duration(days: deltaDays));
|
|
||||||
_scheduleFuture = _scheduleService.getDailySchedule(_selectedDate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
final dateStr =
|
|
||||||
'${_selectedDate.year}-${_selectedDate.month.toString().padLeft(2, '0')}-${_selectedDate.day.toString().padLeft(2, '0')}';
|
|
||||||
final today = DateTime.now();
|
|
||||||
final isToday = DateTime(_selectedDate.year, _selectedDate.month, _selectedDate.day) ==
|
|
||||||
DateTime(today.year, today.month, today.day);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Today on the Road'),
|
|
||||||
elevation: 0,
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.logout),
|
|
||||||
tooltip: 'Logout',
|
|
||||||
onPressed: _handleLogout,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? const [Color(0xFF0B1220), Color(0xFF111827)]
|
|
||||||
: const [Color(0xFFF1F5F9), Color(0xFFE2E8F0)],
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
|
||||||
padding: const EdgeInsets.all(18),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF0F172A), Color(0xFF1D4ED8)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.16),
|
|
||||||
blurRadius: 22,
|
|
||||||
offset: const Offset(0, 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
isToday ? 'Vandaag' : 'Geplande datum',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.78),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
dateStr,
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
'Routes, stops en status in één overzicht',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.84),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
alignment: WrapAlignment.end,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: () => _moveDay(-1),
|
|
||||||
icon: const Icon(Icons.chevron_left, size: 18),
|
|
||||||
label: const Text('Vorige dag'),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
side: BorderSide(color: Colors.white.withOpacity(0.28)),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: _selectDate,
|
|
||||||
icon: const Icon(Icons.calendar_today, size: 18),
|
|
||||||
label: const Text('Kies datum'),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
side: BorderSide(color: Colors.white.withOpacity(0.28)),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: () => _moveDay(1),
|
|
||||||
icon: const Icon(Icons.chevron_right, size: 18),
|
|
||||||
label: const Text('Volgende dag'),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
side: BorderSide(color: Colors.white.withOpacity(0.28)),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FutureBuilder<schedule.DailySchedule>(
|
|
||||||
future: _scheduleFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
// Auto-logout on session expiry
|
|
||||||
if (snapshot.error is UnauthorizedException) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_handleLogout();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
size: 64,
|
|
||||||
color: colorScheme.error,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
snapshot.error is UnauthorizedException
|
|
||||||
? 'Session Expired'
|
|
||||||
: 'Failed to load schedule',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
snapshot.error is UnauthorizedException
|
|
||||||
? 'Redirecting to login...'
|
|
||||||
: snapshot.error.toString(),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
if (snapshot.error is! UnauthorizedException) ...[
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
FilledButton.icon(
|
|
||||||
onPressed: _refreshSchedule,
|
|
||||||
icon: const Icon(Icons.refresh),
|
|
||||||
label: const Text('Retry'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!snapshot.hasData || snapshot.data!.routes.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.calendar_today,
|
|
||||||
size: 64,
|
|
||||||
color: colorScheme.outline,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Geen actieve ritten',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Je hebt geen geplande ritten voor deze dag',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final routes = [...snapshot.data!.routes]
|
|
||||||
..sort((a, b) => a.startTime.compareTo(b.startTime));
|
|
||||||
final scheduledRoutes = routes.where((r) => r.status == 'scheduled').toList();
|
|
||||||
final inProgressRoutes = routes.where((r) => r.status == 'in_progress').toList();
|
|
||||||
|
|
||||||
// Merge and group by hour
|
|
||||||
final allActiveRoutes = [...inProgressRoutes, ...scheduledRoutes]
|
|
||||||
..sort((a, b) => a.startTime.compareTo(b.startTime));
|
|
||||||
final hourGroups = _groupByHour(allActiveRoutes);
|
|
||||||
final sortedHours = hourGroups.keys.toList()..sort();
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
|
||||||
onRefresh: _refreshSchedule,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
|
||||||
children: [
|
|
||||||
if (allActiveRoutes.isEmpty)
|
|
||||||
Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
|
||||||
child: Text(
|
|
||||||
'Geen actieve ritten voor deze dag',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
...sortedHours.map((hour) {
|
|
||||||
final routesForHour = hourGroups[hour] ?? [];
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${hour.toString().padLeft(2, '0')}:00 uur',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 1,
|
|
||||||
color: colorScheme.outlineVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...routesForHour.map((route) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: RouteCard(route: route, onTap: () {}),
|
|
||||||
)),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../services/auth_service.dart';
|
|
||||||
import '../services/error_log_service.dart';
|
|
||||||
import '../widgets/custom_input_field.dart';
|
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
|
||||||
const LoginScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<LoginScreen> createState() => _LoginScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginScreenState extends State<LoginScreen> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final _userCodeController = TextEditingController();
|
|
||||||
final _passwordController = TextEditingController();
|
|
||||||
final _authService = AuthService();
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
|
||||||
String? _errorMessage;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_userCodeController.dispose();
|
|
||||||
_passwordController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleLogin() async {
|
|
||||||
if (!_formKey.currentState!.validate()) return;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
_errorMessage = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final input = _userCodeController.text.trim();
|
|
||||||
final parsedUserCode = int.tryParse(input);
|
|
||||||
if (parsedUserCode == null) {
|
|
||||||
await ErrorLogService.report(
|
|
||||||
service: 'bus_terminal',
|
|
||||||
partOfService: 'LoginScreen._handleLogin',
|
|
||||||
error: 'User code must be a number',
|
|
||||||
);
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = 'User code must be a number';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _authService.login(
|
|
||||||
userCode: parsedUserCode,
|
|
||||||
password: _passwordController.text,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
// Navigate to daily schedule screen
|
|
||||||
Navigator.of(context).pushReplacementNamed('/schedule');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await ErrorLogService.report(
|
|
||||||
service: 'bus_terminal',
|
|
||||||
partOfService: 'LoginScreen._handleLogin.catch',
|
|
||||||
error: e.toString(),
|
|
||||||
);
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = e.toString().replaceAll('Exception: ', '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _validateUserCode(String? value) {
|
|
||||||
final v = value?.trim();
|
|
||||||
if (v?.isEmpty ?? true) {
|
|
||||||
return 'User code is required';
|
|
||||||
}
|
|
||||||
if (int.tryParse(v!) == null) {
|
|
||||||
return 'User code must be a number';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _validatePassword(String? value) {
|
|
||||||
if (value?.isEmpty ?? true) {
|
|
||||||
return 'Password is required';
|
|
||||||
}
|
|
||||||
if ((value?.length ?? 0) < 3) {
|
|
||||||
return 'Password must be at least 3 characters';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? const [Color(0xFF0B1220), Color(0xFF111827), Color(0xFF1D4ED8)]
|
|
||||||
: const [Color(0xFFF1F5F9), Color(0xFFE2E8F0), Color(0xFFBAE6FD)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(20.0),
|
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF0F172A), Color(0xFF1D4ED8)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.18),
|
|
||||||
blurRadius: 24,
|
|
||||||
offset: const Offset(0, 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 56,
|
|
||||||
height: 56,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.16),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.directions_bus,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Bus Terminal',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'Driver access dashboard',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.82),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 18),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colorScheme.surface.withOpacity(0.92),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
border: Border.all(color: colorScheme.outline.withOpacity(0.14)),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.08),
|
|
||||||
blurRadius: 22,
|
|
||||||
offset: const Offset(0, 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Driver Login',
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Sign in to view your route, stops and live navigation.',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 28),
|
|
||||||
|
|
||||||
if (_errorMessage != null) ...[
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colorScheme.error.withOpacity(0.12),
|
|
||||||
border: Border.all(color: colorScheme.error.withOpacity(0.35)),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.error_outline, color: colorScheme.error),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_errorMessage!,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
|
|
||||||
CustomInputField(
|
|
||||||
label: 'User Code',
|
|
||||||
hint: 'Enter your user code',
|
|
||||||
controller: _userCodeController,
|
|
||||||
validator: _validateUserCode,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
CustomInputField(
|
|
||||||
label: 'Password',
|
|
||||||
hint: 'Enter your password',
|
|
||||||
controller: _passwordController,
|
|
||||||
obscureText: true,
|
|
||||||
validator: _validatePassword,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 28),
|
|
||||||
|
|
||||||
FilledButton(
|
|
||||||
onPressed: _isLoading ? null : _handleLogin,
|
|
||||||
child: _isLoading
|
|
||||||
? SizedBox(
|
|
||||||
height: 24,
|
|
||||||
width: 24,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(colorScheme.onPrimary),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const Text('Login'),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_statusPill(Icons.shield_outlined, 'Secure access'),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
_statusPill(Icons.route_outlined, 'Live route data'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _statusPill(IconData icon, String label) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.72),
|
|
||||||
borderRadius: BorderRadius.circular(999),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: 16),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,913 +0,0 @@
|
|||||||
// ignore_for_file: deprecated_member_use
|
|
||||||
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import '../models/navigation.dart';
|
|
||||||
import '../models/schedule.dart' as schedule;
|
|
||||||
import '../services/navigation_service.dart';
|
|
||||||
import '../services/schedule_service.dart';
|
|
||||||
|
|
||||||
class NavigationScreen extends StatefulWidget {
|
|
||||||
final schedule.Route route;
|
|
||||||
final List<NavigationPoint>? initialStops;
|
|
||||||
|
|
||||||
const NavigationScreen({
|
|
||||||
super.key,
|
|
||||||
required this.route,
|
|
||||||
this.initialStops,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<NavigationScreen> createState() => _NavigationScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavigationScreenState extends State<NavigationScreen> {
|
|
||||||
final _navigationService = NavigationService();
|
|
||||||
final _scheduleService = ScheduleService();
|
|
||||||
late MapController _mapController;
|
|
||||||
|
|
||||||
NavigationRoute? _currentRoute;
|
|
||||||
NavigationPoint? _currentLocation;
|
|
||||||
bool _isLoading = false;
|
|
||||||
String? _errorMessage;
|
|
||||||
List<NavigationRoute>? _alternativeRoutes;
|
|
||||||
List<NavigationPoint>? _stops;
|
|
||||||
String? _stopsFetchError;
|
|
||||||
int _currentStopIndex = 0;
|
|
||||||
final Set<int> _completedStops = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_mapController = MapController();
|
|
||||||
_initializeNavigation();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initializeNavigation() async {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
_errorMessage = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get current location
|
|
||||||
final currentLoc = await _navigationService.getCurrentLocation();
|
|
||||||
setState(() => _currentLocation = currentLoc);
|
|
||||||
|
|
||||||
// Use provided initial stops when available, otherwise fetch from API
|
|
||||||
if (widget.initialStops != null) {
|
|
||||||
_stops = widget.initialStops;
|
|
||||||
_stopsFetchError = null;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
_stops = await _scheduleService.getStops(widget.route.busLine.id);
|
|
||||||
_stopsFetchError = null;
|
|
||||||
} catch (e) {
|
|
||||||
_stops = [];
|
|
||||||
_stopsFetchError = e.toString().replaceAll('Exception: ', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defensive: filter out invalid stops (missing/NaN coordinates) and deduplicate
|
|
||||||
if (_stops != null) {
|
|
||||||
_stops = _stops!
|
|
||||||
.where((s) => s.latitude.isFinite && s.longitude.isFinite)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Remove exact duplicates by lat/lon
|
|
||||||
final seen = <String>{};
|
|
||||||
_stops = _stops!.where((s) {
|
|
||||||
final key = '${s.latitude.toStringAsFixed(6)}|${s.longitude.toStringAsFixed(6)}';
|
|
||||||
if (seen.contains(key)) return false;
|
|
||||||
seen.add(key);
|
|
||||||
return true;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine start and end points: prefer stop locations if available.
|
|
||||||
// If no stops are returned from the API, geocode the stop names.
|
|
||||||
late NavigationPoint startPoint;
|
|
||||||
late NavigationPoint endPoint;
|
|
||||||
|
|
||||||
if (_stops != null && _stops!.length >= 2) {
|
|
||||||
final hasScheduleStops = widget.initialStops != null;
|
|
||||||
|
|
||||||
if (!hasScheduleStops) {
|
|
||||||
final optimized = await _navigationService.getOptimizedRouteThroughStops(stops: _stops!);
|
|
||||||
_stops = optimized.orderedStops;
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_currentRoute = optimized.route;
|
|
||||||
_alternativeRoutes = null;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(optimized.route));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final navRoute = await _navigationService.getRouteThroughStops(stops: _stops!);
|
|
||||||
startPoint = _stops!.first;
|
|
||||||
endPoint = _stops!.last;
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_currentRoute = navRoute;
|
|
||||||
_alternativeRoutes = null;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(navRoute));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else if (_stops != null && _stops!.length == 1) {
|
|
||||||
// With exactly one linked stop, force that stop into the route as start
|
|
||||||
// so the marker is guaranteed to be in-view.
|
|
||||||
startPoint = _stops!.first;
|
|
||||||
|
|
||||||
try {
|
|
||||||
endPoint = await _navigationService.geocodePlace(widget.route.busLine.endStop);
|
|
||||||
} catch (_) {
|
|
||||||
endPoint = NavigationPoint(
|
|
||||||
latitude: startPoint.latitude + 0.01,
|
|
||||||
longitude: startPoint.longitude + 0.01,
|
|
||||||
name: widget.route.busLine.endStop,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Try geocoding names; fall back to conservative defaults on failure
|
|
||||||
try {
|
|
||||||
startPoint = await _navigationService.geocodePlace(widget.route.busLine.startStop);
|
|
||||||
} catch (_) {
|
|
||||||
startPoint = NavigationPoint(
|
|
||||||
latitude: 52.5200,
|
|
||||||
longitude: 13.4050,
|
|
||||||
name: widget.route.busLine.startStop,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
endPoint = await _navigationService.geocodePlace(widget.route.busLine.endStop);
|
|
||||||
} catch (_) {
|
|
||||||
endPoint = NavigationPoint(
|
|
||||||
latitude: 52.5300,
|
|
||||||
longitude: 13.4150,
|
|
||||||
name: widget.route.busLine.endStop,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get route without waypoints
|
|
||||||
final navRoute = await _navigationService.getRoute(start: startPoint, end: endPoint);
|
|
||||||
|
|
||||||
// Get alternative routes (but cap to a small number)
|
|
||||||
final alternatives = await _navigationService.getMultipleRoutes(start: startPoint, end: endPoint);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_currentRoute = navRoute;
|
|
||||||
_alternativeRoutes = alternatives.take(2).toList();
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fit map to route bounds after first frame so MapController is attached
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(navRoute));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = e.toString().replaceAll('Exception: ', '');
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _fitMapToRoute(NavigationRoute route) {
|
|
||||||
// Build list of points to include in bounds, filter invalids
|
|
||||||
final allPoints = <LatLng>[];
|
|
||||||
allPoints.addAll(route.getAllPoints().where((p) => p.latitude.isFinite && p.longitude.isFinite).map((point) => LatLng(point.latitude, point.longitude)));
|
|
||||||
allPoints.addAll((_stops ?? []).where((s) => s.latitude.isFinite && s.longitude.isFinite).map((stop) => LatLng(stop.latitude, stop.longitude)));
|
|
||||||
if (_currentLocation != null && _currentLocation!.latitude.isFinite && _currentLocation!.longitude.isFinite) {
|
|
||||||
allPoints.add(LatLng(_currentLocation!.latitude, _currentLocation!.longitude));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allPoints.isEmpty) return;
|
|
||||||
|
|
||||||
double minLat = allPoints.first.latitude;
|
|
||||||
double maxLat = allPoints.first.latitude;
|
|
||||||
double minLon = allPoints.first.longitude;
|
|
||||||
double maxLon = allPoints.first.longitude;
|
|
||||||
|
|
||||||
for (final point in allPoints) {
|
|
||||||
minLat = min(minLat, point.latitude);
|
|
||||||
maxLat = max(maxLat, point.latitude);
|
|
||||||
minLon = min(minLon, point.longitude);
|
|
||||||
maxLon = max(maxLon, point.longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp bounds to a reasonable maximum span to avoid huge map tiles requests
|
|
||||||
const double maxSpanDegrees = 2.0; // ~222km lat span per degree ~111km; 2 degrees ~222km
|
|
||||||
final latSpan = maxLat - minLat;
|
|
||||||
final lonSpan = maxLon - minLon;
|
|
||||||
|
|
||||||
double centerLat = (minLat + maxLat) / 2.0;
|
|
||||||
double centerLon = (minLon + maxLon) / 2.0;
|
|
||||||
|
|
||||||
double halfLat = latSpan / 2.0;
|
|
||||||
double halfLon = lonSpan / 2.0;
|
|
||||||
|
|
||||||
if (latSpan > maxSpanDegrees) halfLat = maxSpanDegrees / 2.0;
|
|
||||||
if (lonSpan > maxSpanDegrees) halfLon = maxSpanDegrees / 2.0;
|
|
||||||
|
|
||||||
final clampedMinLat = centerLat - halfLat;
|
|
||||||
final clampedMaxLat = centerLat + halfLat;
|
|
||||||
final clampedMinLon = centerLon - halfLon;
|
|
||||||
final clampedMaxLon = centerLon + halfLon;
|
|
||||||
|
|
||||||
final bounds = LatLngBounds(
|
|
||||||
LatLng(clampedMinLat, clampedMinLon),
|
|
||||||
LatLng(clampedMaxLat, clampedMaxLon),
|
|
||||||
);
|
|
||||||
|
|
||||||
_mapController.fitBounds(
|
|
||||||
bounds,
|
|
||||||
options: const FitBoundsOptions(padding: EdgeInsets.all(100)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _nextUncompletedStopIndex([int startIndex = 0]) {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return 0;
|
|
||||||
|
|
||||||
for (var index = startIndex; index < _stops!.length; index++) {
|
|
||||||
if (!_completedStops.contains(index)) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _stops!.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refreshRouteFromIndex(int index) async {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return;
|
|
||||||
if (index < 0 || index >= _stops!.length) return;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
_errorMessage = null;
|
|
||||||
_currentStopIndex = index;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final remainingStops = _stops!.sublist(index);
|
|
||||||
final navRoute = remainingStops.length >= 2
|
|
||||||
? await _navigationService.getRouteThroughStops(stops: remainingStops)
|
|
||||||
: _currentRoute;
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
if (navRoute != null) {
|
|
||||||
_currentRoute = navRoute;
|
|
||||||
}
|
|
||||||
_alternativeRoutes = null;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (navRoute != null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(navRoute));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = e.toString().replaceAll('Exception: ', '');
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _focusStop(int index) async {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return;
|
|
||||||
if (index < 0 || index >= _stops!.length) return;
|
|
||||||
await _refreshRouteFromIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _markStopCompleted(int index) async {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return;
|
|
||||||
if (index < 0 || index >= _stops!.length) return;
|
|
||||||
|
|
||||||
// When this screen is opened from schedule overview, route.id maps to scheduleId.
|
|
||||||
if (widget.initialStops != null) {
|
|
||||||
try {
|
|
||||||
await _scheduleService.markScheduleStopPassed(
|
|
||||||
scheduleId: widget.route.id,
|
|
||||||
stopOrder: index + 1,
|
|
||||||
actualPassedAt: DateTime.now(),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = 'Stop update mislukt: ${e.toString().replaceAll('Exception: ', '')}';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_completedStops.add(index);
|
|
||||||
});
|
|
||||||
|
|
||||||
final nextIndex = _nextUncompletedStopIndex(index + 1);
|
|
||||||
await _refreshRouteFromIndex(nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
double get _routeProgress {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return 0;
|
|
||||||
if (_stops!.length == 1) return 1;
|
|
||||||
return _currentStopIndex / (_stops!.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color get _routeColor => Color.lerp(Colors.blue, Colors.orange, _routeProgress) ?? Colors.blue;
|
|
||||||
|
|
||||||
Color _stopColor(int index) {
|
|
||||||
if (_completedStops.contains(index) || index < _currentStopIndex) return Colors.green;
|
|
||||||
if (index == _currentStopIndex) return Colors.orange;
|
|
||||||
return Colors.blueGrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
IconData _stopIcon(int index) {
|
|
||||||
if (_completedStops.contains(index) || index < _currentStopIndex) return Icons.check_circle;
|
|
||||||
if (index == _currentStopIndex) return Icons.radio_button_checked;
|
|
||||||
return Icons.location_on;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isStopCompleted(int index) => _completedStops.contains(index) || index < _currentStopIndex;
|
|
||||||
|
|
||||||
bool _hasStopNearPoint(NavigationPoint point, {double threshold = 0.00005}) {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return false;
|
|
||||||
|
|
||||||
for (final stop in _stops!) {
|
|
||||||
final latDiff = (stop.latitude - point.latitude).abs();
|
|
||||||
final lonDiff = (stop.longitude - point.longitude).abs();
|
|
||||||
if (latDiff <= threshold && lonDiff <= threshold) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationPoint? get _currentStopPoint {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return null;
|
|
||||||
return _stops![_currentStopIndex.clamp(0, _stops!.length - 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationPoint? get _nextStopPoint {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return null;
|
|
||||||
final nextIndex = _nextUncompletedStopIndex(_currentStopIndex + 1);
|
|
||||||
return _stops![nextIndex.clamp(0, _stops!.length - 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
double get _completionRatio {
|
|
||||||
if (_stops == null || _stops!.isEmpty) return 0;
|
|
||||||
return _completedStops.length / _stops!.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text('Route ${widget.route.busLine.lineNumber} Navigation'),
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
body: Row(
|
|
||||||
children: [
|
|
||||||
// Map area (left pane)
|
|
||||||
Expanded(
|
|
||||||
flex: 6,
|
|
||||||
child: _currentRoute != null
|
|
||||||
? FlutterMap(
|
|
||||||
mapController: _mapController,
|
|
||||||
options: MapOptions(
|
|
||||||
initialCenter: LatLng(
|
|
||||||
_currentRoute!.start.latitude,
|
|
||||||
_currentRoute!.start.longitude,
|
|
||||||
),
|
|
||||||
initialZoom: 13.0,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TileLayer(
|
|
||||||
urlTemplate:
|
|
||||||
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
userAgentPackageName: 'com.example.bus_terminal',
|
|
||||||
),
|
|
||||||
MarkerLayer(
|
|
||||||
markers: [
|
|
||||||
...(_stops ?? []).asMap().entries.map((entry) {
|
|
||||||
final index = entry.key;
|
|
||||||
final stop = entry.value;
|
|
||||||
final color = _stopColor(index);
|
|
||||||
|
|
||||||
return Marker(
|
|
||||||
point: LatLng(stop.latitude, stop.longitude),
|
|
||||||
width: 46,
|
|
||||||
height: 46,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.white,
|
|
||||||
width: 3,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.22),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
_stopIcon(index),
|
|
||||||
color: Colors.white,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withOpacity(0.35),
|
|
||||||
borderRadius: BorderRadius.circular(999),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'${index + 1}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
// Start marker
|
|
||||||
if (!_hasStopNearPoint(_currentRoute!.start))
|
|
||||||
Marker(
|
|
||||||
point: LatLng(
|
|
||||||
_currentRoute!.start.latitude,
|
|
||||||
_currentRoute!.start.longitude,
|
|
||||||
),
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.green,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.white,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.location_on,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// End marker
|
|
||||||
if (!_hasStopNearPoint(_currentRoute!.end))
|
|
||||||
Marker(
|
|
||||||
point: LatLng(
|
|
||||||
_currentRoute!.end.latitude,
|
|
||||||
_currentRoute!.end.longitude,
|
|
||||||
),
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.white,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.location_on,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Current location marker
|
|
||||||
if (_currentLocation != null)
|
|
||||||
Marker(
|
|
||||||
point: LatLng(
|
|
||||||
_currentLocation!.latitude,
|
|
||||||
_currentLocation!.longitude,
|
|
||||||
),
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.blue,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.white,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.directions_bus,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
PolylineLayer(
|
|
||||||
polylines: [
|
|
||||||
// Main route
|
|
||||||
Polyline(
|
|
||||||
points: _currentRoute!
|
|
||||||
.getAllPoints()
|
|
||||||
.map((p) => LatLng(p.latitude, p.longitude))
|
|
||||||
.toList(),
|
|
||||||
color: _routeColor,
|
|
||||||
strokeWidth: 5,
|
|
||||||
),
|
|
||||||
// Alternative routes (lighter)
|
|
||||||
if (_alternativeRoutes != null)
|
|
||||||
...List.generate(
|
|
||||||
_alternativeRoutes!.length,
|
|
||||||
(i) => Polyline(
|
|
||||||
points: _alternativeRoutes![i]
|
|
||||||
.getAllPoints()
|
|
||||||
.map((p) => LatLng(p.latitude, p.longitude))
|
|
||||||
.toList(),
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: (_isLoading
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: Center(
|
|
||||||
child: _errorMessage != null
|
|
||||||
? Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
size: 64,
|
|
||||||
color: Colors.red[300],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Failed to load route',
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
_errorMessage!,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: const Text('No route'))),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Vertical divider
|
|
||||||
Container(width: 1, color: Colors.grey[200]),
|
|
||||||
|
|
||||||
// Stops / Info panel (right pane)
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16),
|
|
||||||
bottomLeft: Radius.circular(16),
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.1),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(-2, 0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF0F172A), Color(0xFF1D4ED8)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.12),
|
|
||||||
blurRadius: 18,
|
|
||||||
offset: const Offset(0, 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 42,
|
|
||||||
height: 42,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.18),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.route,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Route ${widget.route.busLine.lineNumber}',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
'${widget.route.busLine.startStop} → ${widget.route.busLine.endStop}',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.85),
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(999),
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
minHeight: 10,
|
|
||||||
value: _completionRatio,
|
|
||||||
backgroundColor: Colors.white.withOpacity(0.12),
|
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.amberAccent),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${(_completionRatio * 100).round()}% afgerond',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${_completedStops.length}/${_stops?.length ?? 0} stops',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.85),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildSummaryPill(
|
|
||||||
'Huidig',
|
|
||||||
_currentStopPoint?.name ?? 'Geen stop',
|
|
||||||
Icons.radio_button_checked,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: _buildSummaryPill(
|
|
||||||
'Volgend',
|
|
||||||
_nextStopPoint?.name ?? 'Einde route',
|
|
||||||
Icons.skip_next,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: const Color(0xFF0F172A),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(14),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: _stops == null || _stops!.isEmpty || _isStopCompleted(_currentStopIndex)
|
|
||||||
? null
|
|
||||||
: () => _markStopCompleted(_currentStopIndex),
|
|
||||||
icon: const Icon(Icons.check_circle_outline),
|
|
||||||
label: const Text('Markeer huidige stop afgerond'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[100],
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
_legendDot(Colors.green, 'Afgerond'),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
_legendDot(Colors.orange, 'Actief'),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
_legendDot(Colors.blueGrey, 'Te doen'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_stopsFetchError != null) ...[
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red.withOpacity(0.08),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: Border.all(color: Colors.red.withOpacity(0.25)),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Stops konden niet geladen worden voor buslijn ${widget.route.busLine.id}: $_stopsFetchError',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
'Stops',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleMedium
|
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Expanded(
|
|
||||||
child: _stops == null || _stops!.isEmpty
|
|
||||||
? Center(
|
|
||||||
child: Text(
|
|
||||||
'No stops available for this line',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.separated(
|
|
||||||
itemCount: _stops!.length,
|
|
||||||
separatorBuilder: (context, index) => const Divider(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final s = _stops![index];
|
|
||||||
final color = _stopColor(index);
|
|
||||||
final completed = _isStopCompleted(index);
|
|
||||||
final isCurrent = index == _currentStopIndex;
|
|
||||||
return ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
radius: 14,
|
|
||||||
backgroundColor: color,
|
|
||||||
child: Icon(
|
|
||||||
_stopIcon(index),
|
|
||||||
color: Colors.white,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(s.name ?? 'Stop ${index + 1}'),
|
|
||||||
subtitle: Text('${s.latitude.toStringAsFixed(5)}, ${s.longitude.toStringAsFixed(5)}'),
|
|
||||||
trailing: completed
|
|
||||||
? const Chip(
|
|
||||||
label: Text('Afgerond'),
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
)
|
|
||||||
: TextButton.icon(
|
|
||||||
onPressed: () => _markStopCompleted(index),
|
|
||||||
icon: const Icon(Icons.check, size: 18),
|
|
||||||
label: Text(isCurrent ? 'Afronden' : 'Klaarzetten'),
|
|
||||||
),
|
|
||||||
onTap: () => _focusStop(index),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _legendDot(Color color, String label) {
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSummaryPill(String title, String value, IconData icon) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.12),
|
|
||||||
borderRadius: BorderRadius.circular(14),
|
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.16)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Icon(icon, color: Colors.white, size: 18),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.75),
|
|
||||||
letterSpacing: 0.2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../models/schedule.dart' as sched;
|
|
||||||
import '../services/schedule_service.dart';
|
|
||||||
|
|
||||||
class RecurringSchedulesScreen extends StatefulWidget {
|
|
||||||
const RecurringSchedulesScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RecurringSchedulesScreen> createState() => _RecurringSchedulesScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecurringSchedulesScreenState extends State<RecurringSchedulesScreen> {
|
|
||||||
final _scheduleService = ScheduleService();
|
|
||||||
late Future<List<sched.ServiceSchedule>> _schedulesFuture;
|
|
||||||
String _selectedSortBy = 'line'; // 'line', 'time', 'weekday'
|
|
||||||
final Set<int> _expandedLineIds = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadRecurringSchedules();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadRecurringSchedules() {
|
|
||||||
_schedulesFuture = _scheduleService.getRecurringSchedules();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatWeekdays(List<int> weekdays) {
|
|
||||||
const dayLabels = ['Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za'];
|
|
||||||
if (weekdays.isEmpty) return 'Eenmalig';
|
|
||||||
if (weekdays.length == 7) return 'Dagelijks';
|
|
||||||
|
|
||||||
final sorted = weekdays.toSet().toList()..sort((a, b) => a.compareTo(b));
|
|
||||||
return sorted.map((d) => dayLabels[d]).join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatTime(DateTime dt) {
|
|
||||||
return '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
List<sched.ServiceSchedule> _sortSchedules(List<sched.ServiceSchedule> schedules) {
|
|
||||||
final sorted = List<sched.ServiceSchedule>.from(schedules);
|
|
||||||
|
|
||||||
switch (_selectedSortBy) {
|
|
||||||
case 'line':
|
|
||||||
sorted.sort((a, b) => a.lineNumber.compareTo(b.lineNumber));
|
|
||||||
break;
|
|
||||||
case 'time':
|
|
||||||
sorted.sort((a, b) => a.startTime.compareTo(b.startTime));
|
|
||||||
break;
|
|
||||||
case 'weekday':
|
|
||||||
sorted.sort((a, b) {
|
|
||||||
final aFirst = a.weekdays.isEmpty ? 7 : a.weekdays.first;
|
|
||||||
final bFirst = b.weekdays.isEmpty ? 7 : b.weekdays.first;
|
|
||||||
return aFirst.compareTo(bFirst);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<int, List<sched.ServiceSchedule>> _groupByLine(List<sched.ServiceSchedule> schedules) {
|
|
||||||
final grouped = <int, List<sched.ServiceSchedule>>{};
|
|
||||||
for (final schedule in schedules) {
|
|
||||||
grouped.putIfAbsent(schedule.lineNumber, () => []).add(schedule);
|
|
||||||
}
|
|
||||||
return grouped;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Vaste Diensten'),
|
|
||||||
centerTitle: true,
|
|
||||||
actions: [
|
|
||||||
PopupMenuButton<String>(
|
|
||||||
onSelected: (value) {
|
|
||||||
setState(() => _selectedSortBy = value);
|
|
||||||
},
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'line',
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.sort_by_alpha, color: _selectedSortBy == 'line' ? colorScheme.primary : null),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Lijn'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'time',
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.schedule, color: _selectedSortBy == 'time' ? colorScheme.primary : null),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Tijd'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'weekday',
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.calendar_today, color: _selectedSortBy == 'weekday' ? colorScheme.primary : null),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Weekdag'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Icon(Icons.filter_list),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: FutureBuilder<List<sched.ServiceSchedule>>(
|
|
||||||
future: _schedulesFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.error_outline, size: 48, color: colorScheme.error),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Fout bij laden: ${snapshot.error}',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _loadRecurringSchedules());
|
|
||||||
},
|
|
||||||
child: const Text('Opnieuw proberen'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final allSchedules = snapshot.data ?? [];
|
|
||||||
// Filter to show only truly recurring schedules (with weekdays > 0)
|
|
||||||
final schedules = allSchedules.where((s) => s.weekdays.isNotEmpty).toList();
|
|
||||||
|
|
||||||
if (schedules.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.event_note_outlined, size: 48, color: colorScheme.outline),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Geen herhaalde diensten gevonden',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Herhaalde diensten worden hier weergegeven',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final sortedSchedules = _sortSchedules(schedules);
|
|
||||||
final groupedByLine = _groupByLine(sortedSchedules);
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 24),
|
|
||||||
itemCount: groupedByLine.length,
|
|
||||||
itemBuilder: (context, lineIndex) {
|
|
||||||
final lineNumber = groupedByLine.keys.toList()[lineIndex];
|
|
||||||
final lineSchedules = groupedByLine[lineNumber] ?? [];
|
|
||||||
final isExpanded = _expandedLineIds.contains(lineNumber);
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
if (isExpanded) {
|
|
||||||
_expandedLineIds.remove(lineNumber);
|
|
||||||
} else {
|
|
||||||
_expandedLineIds.add(lineNumber);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Lijn $lineNumber',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${lineSchedules.length} diensten',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
isExpanded ? Icons.expand_less : Icons.expand_more,
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isExpanded)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: lineSchedules.length,
|
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
|
||||||
itemBuilder: (context, scheduleIndex) {
|
|
||||||
final schedule = lineSchedules[scheduleIndex];
|
|
||||||
return _ScheduleCard(schedule: schedule, formatTime: _formatTime, formatWeekdays: _formatWeekdays);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ScheduleCard extends StatelessWidget {
|
|
||||||
final sched.ServiceSchedule schedule;
|
|
||||||
final String Function(DateTime) formatTime;
|
|
||||||
final String Function(List<int>) formatWeekdays;
|
|
||||||
|
|
||||||
const _ScheduleCard({
|
|
||||||
required this.schedule,
|
|
||||||
required this.formatTime,
|
|
||||||
required this.formatWeekdays,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
elevation: 1,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${formatTime(schedule.startTime)} - ${formatTime(schedule.endTime)}',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
schedule.busName,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: [
|
|
||||||
Chip(
|
|
||||||
label: Text(
|
|
||||||
formatWeekdays(schedule.weekdays),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
avatar: const Icon(Icons.calendar_today, size: 16),
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,674 +0,0 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../models/schedule.dart' as sched;
|
|
||||||
import '../models/navigation.dart';
|
|
||||||
import '../services/auth_service.dart';
|
|
||||||
import '../services/schedule_service.dart';
|
|
||||||
import '../screens/navigation_screen.dart';
|
|
||||||
import '../screens/recurring_schedules_screen.dart';
|
|
||||||
|
|
||||||
class ScheduleOverviewScreen extends StatefulWidget {
|
|
||||||
const ScheduleOverviewScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ScheduleOverviewScreen> createState() => _ScheduleOverviewScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ScheduleOverviewScreenState extends State<ScheduleOverviewScreen> with TickerProviderStateMixin {
|
|
||||||
final _scheduleService = ScheduleService();
|
|
||||||
final _authService = AuthService();
|
|
||||||
|
|
||||||
late Future<List<sched.ServiceSchedule>> _scheduleFuture;
|
|
||||||
DateTime _selectedDate = DateTime.now();
|
|
||||||
late TabController _tabController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_scheduleFuture = _scheduleService.getSchedules(_selectedDate);
|
|
||||||
_tabController = TabController(length: 3, vsync: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleLogout() async {
|
|
||||||
await _authService.logout();
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).pushReplacementNamed('/login');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refreshSchedules() async {
|
|
||||||
setState(() {
|
|
||||||
_scheduleFuture = _scheduleService.getSchedules(_selectedDate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _selectDate() async {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
final picked = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: _selectedDate,
|
|
||||||
firstDate: DateTime.now().subtract(const Duration(days: 7)),
|
|
||||||
lastDate: DateTime.now().add(const Duration(days: 30)),
|
|
||||||
builder: (context, child) {
|
|
||||||
return Theme(
|
|
||||||
data: Theme.of(context).copyWith(
|
|
||||||
dialogBackgroundColor: colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: child!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (picked != null && picked != _selectedDate) {
|
|
||||||
setState(() {
|
|
||||||
_selectedDate = picked;
|
|
||||||
});
|
|
||||||
_refreshSchedules();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showScheduleDetails(sched.ServiceSchedule svc) async {
|
|
||||||
try {
|
|
||||||
final stops = await _scheduleService.getScheduleStops(svc.id);
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
await showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
showDragHandle: true,
|
|
||||||
builder: (sheetContext) {
|
|
||||||
return DraggableScrollableSheet(
|
|
||||||
expand: false,
|
|
||||||
initialChildSize: 0.75,
|
|
||||||
minChildSize: 0.45,
|
|
||||||
maxChildSize: 0.95,
|
|
||||||
builder: (context, scrollController) {
|
|
||||||
return ListView(
|
|
||||||
controller: scrollController,
|
|
||||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Lijn ${svc.lineNumber}',
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
svc.busName,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${_formatTime(svc.startTime)} - ${_formatTime(svc.endTime)}',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: [
|
|
||||||
_InfoChip(label: svc.status),
|
|
||||||
_InfoChip(label: svc.isRecurring ? _weekdayLabel(svc.weekdays) : 'Eenmalig'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: FilledButton.icon(
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
await _scheduleService.updateScheduleStatus(
|
|
||||||
svc.id,
|
|
||||||
'in_progress',
|
|
||||||
date: svc.startTime,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
Navigator.of(sheetContext).pop();
|
|
||||||
|
|
||||||
final routeObj = sched.Route(
|
|
||||||
id: svc.id,
|
|
||||||
busLine: sched.BusLine(
|
|
||||||
id: svc.busLineId,
|
|
||||||
lineNumber: svc.lineNumber,
|
|
||||||
startStop: '',
|
|
||||||
endStop: '',
|
|
||||||
estimatedDuration: 0,
|
|
||||||
),
|
|
||||||
busType: sched.BusType(
|
|
||||||
id: svc.busId,
|
|
||||||
name: svc.busName,
|
|
||||||
seatCapacity: 0,
|
|
||||||
licensePlate: '',
|
|
||||||
),
|
|
||||||
startTime: svc.startTime,
|
|
||||||
endTime: svc.endTime,
|
|
||||||
status: 'in_progress',
|
|
||||||
);
|
|
||||||
|
|
||||||
final navStops = stops
|
|
||||||
.map((s) => NavigationPoint(
|
|
||||||
latitude: s.latitude,
|
|
||||||
longitude: s.longitude,
|
|
||||||
name: s.name,
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => NavigationScreen(route: routeObj, initialStops: navStops),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (navErr) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Navigatiefout: ${navErr.toString()}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Status bijwerken mislukt: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.navigation),
|
|
||||||
label: const Text('Start route'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Haltes',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
if (stops.isEmpty)
|
|
||||||
Text(
|
|
||||||
'Geen haltes gekoppeld aan deze dienst.',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
...stops.map((stop) {
|
|
||||||
final departureTime = svc.startTime.add(
|
|
||||||
Duration(minutes: stop.estimatedArrivalMinutes),
|
|
||||||
);
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
dense: true,
|
|
||||||
title: Text('${stop.order}. ${stop.name}'),
|
|
||||||
subtitle: Text('${stop.estimatedArrivalMinutes} min na vertrek'),
|
|
||||||
trailing: Text(_formatTime(departureTime)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Kon dienstdetails niet laden: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Diensten'),
|
|
||||||
centerTitle: true,
|
|
||||||
elevation: 0,
|
|
||||||
bottom: TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
tabs: const [
|
|
||||||
Tab(icon: Icon(Icons.calendar_today), text: 'Dagschema'),
|
|
||||||
Tab(icon: Icon(Icons.repeat), text: 'Vaste diensten'),
|
|
||||||
Tab(icon: Icon(Icons.timeline), text: 'Voortgang'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.logout),
|
|
||||||
tooltip: 'Logout',
|
|
||||||
onPressed: _handleLogout,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: [
|
|
||||||
_DailyScheduleTab(
|
|
||||||
scheduleService: _scheduleService,
|
|
||||||
selectedDate: _selectedDate,
|
|
||||||
onSelectDate: _selectDate,
|
|
||||||
onRefresh: _refreshSchedules,
|
|
||||||
onShowDetails: _showScheduleDetails,
|
|
||||||
scheduleFuture: _scheduleFuture,
|
|
||||||
),
|
|
||||||
const RecurringSchedulesScreen(),
|
|
||||||
_ProgressTab(
|
|
||||||
scheduleService: _scheduleService,
|
|
||||||
selectedDate: _selectedDate,
|
|
||||||
scheduleFuture: _scheduleFuture,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ProgressTab extends StatelessWidget {
|
|
||||||
final ScheduleService scheduleService;
|
|
||||||
final DateTime selectedDate;
|
|
||||||
final Future<List<sched.ServiceSchedule>> scheduleFuture;
|
|
||||||
|
|
||||||
const _ProgressTab({
|
|
||||||
required this.scheduleService,
|
|
||||||
required this.selectedDate,
|
|
||||||
required this.scheduleFuture,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final dateStr = '${selectedDate.year}-${selectedDate.month.toString().padLeft(2, '0')}-${selectedDate.day.toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('Voortgang voor $dateStr', style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
// refresh handled by parent via reselecting date; show a simple feedback
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Ververs de datum in Dagschema-tab om andere datum te laden')));
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.info_outline),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FutureBuilder<List<sched.ServiceSchedule>>(
|
|
||||||
future: scheduleFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator());
|
|
||||||
if (snapshot.hasError) return Center(child: Text('Fout bij laden: ${snapshot.error}'));
|
|
||||||
|
|
||||||
final schedules = snapshot.data ?? [];
|
|
||||||
if (schedules.isEmpty) return Center(child: Text('Geen diensten op $dateStr'));
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
itemCount: schedules.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final svc = schedules[index];
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
title: Text('Lijn ${svc.lineNumber} — ${svc.busName}'),
|
|
||||||
subtitle: Text(_formatTime(svc.startTime)),
|
|
||||||
trailing: TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
final progress = await scheduleService.getScheduleProgress(svc.id, selectedDate);
|
|
||||||
if (!context.mounted) return;
|
|
||||||
await showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder: (ctx) {
|
|
||||||
final stops = (progress['stops'] as List<dynamic>?) ?? [];
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('Voortgang — Lijn ${svc.lineNumber}', style: Theme.of(ctx).textTheme.headlineSmall),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text('Status: ${progress['schedule']?['status'] ?? 'onbekend'}'),
|
|
||||||
Text('Driver state: ${progress['driverState'] ?? 'onbekend'}'),
|
|
||||||
Text('Passed: ${progress['passedStopCount'] ?? 0} / ${progress['totalStopCount'] ?? 0}'),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
const Text('Haltes:'),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: stops.length,
|
|
||||||
itemBuilder: (c, i) {
|
|
||||||
final s = stops[i] as Map<String, dynamic>;
|
|
||||||
return ListTile(
|
|
||||||
dense: true,
|
|
||||||
title: Text('${s['stopOrder']}. ${s['stopName']}'),
|
|
||||||
subtitle: Text('Scheduled: ${s['scheduledPassedAt'] ?? '-'}\nActual: ${s['actualPassedAt'] ?? '-'}'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Kon voortgang niet laden: $e')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('Bekijk voortgang'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DailyScheduleTab extends StatelessWidget {
|
|
||||||
final ScheduleService scheduleService;
|
|
||||||
final DateTime selectedDate;
|
|
||||||
final VoidCallback onSelectDate;
|
|
||||||
final VoidCallback onRefresh;
|
|
||||||
final Function(sched.ServiceSchedule) onShowDetails;
|
|
||||||
final Future<List<sched.ServiceSchedule>> scheduleFuture;
|
|
||||||
|
|
||||||
const _DailyScheduleTab({
|
|
||||||
required this.scheduleService,
|
|
||||||
required this.selectedDate,
|
|
||||||
required this.onSelectDate,
|
|
||||||
required this.onRefresh,
|
|
||||||
required this.onShowDetails,
|
|
||||||
required this.scheduleFuture,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
final dateStr =
|
|
||||||
'${selectedDate.year}-${selectedDate.month.toString().padLeft(2, '0')}-${selectedDate.day.toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? const [Color(0xFF0B1220), Color(0xFF111827)]
|
|
||||||
: const [Color(0xFFF1F5F9), Color(0xFFE2E8F0)],
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
|
||||||
padding: const EdgeInsets.all(18),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF0F172A), Color(0xFF1D4ED8)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.16),
|
|
||||||
blurRadius: 22,
|
|
||||||
offset: const Offset(0, 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Geplande diensten',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.78),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
dateStr,
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
'Automatisch berekende eindtijden, haltes en herhalende weekdagen',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.white.withOpacity(0.84),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: onSelectDate,
|
|
||||||
icon: const Icon(Icons.calendar_today, size: 18),
|
|
||||||
label: const Text('Wijzig datum'),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
side: BorderSide(color: Colors.white.withOpacity(0.28)),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FutureBuilder<List<sched.ServiceSchedule>>(
|
|
||||||
future: scheduleFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.error_outline, size: 48, color: colorScheme.error),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('Fout bij laden: ${snapshot.error}', textAlign: TextAlign.center),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: onRefresh,
|
|
||||||
child: const Text('Opnieuw proberen'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final schedules = snapshot.data ?? [];
|
|
||||||
if (schedules.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.event_available_outlined, size: 48, color: colorScheme.outline),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('Geen diensten op $dateStr', style: Theme.of(context).textTheme.titleMedium),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
|
||||||
onRefresh: () async => onRefresh(),
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.fromLTRB(0, 12, 0, 24),
|
|
||||||
itemCount: schedules.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final schedule = schedules[index];
|
|
||||||
return _ScheduleSummaryCard(
|
|
||||||
schedule: schedule,
|
|
||||||
onTap: () => onShowDetails(schedule),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ScheduleSummaryCard extends StatelessWidget {
|
|
||||||
final sched.ServiceSchedule schedule;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
const _ScheduleSummaryCard({
|
|
||||||
required this.schedule,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
|
||||||
elevation: 0,
|
|
||||||
color: colorScheme.surface,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
onTap: onTap,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [colorScheme.primary, colorScheme.tertiary],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Line ${schedule.lineNumber}',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Only show status for one-time schedules; recurring schedules have no fixed status
|
|
||||||
if (!schedule.isRecurring)
|
|
||||||
_InfoChip(label: schedule.status),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
schedule.busName,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${_formatTime(schedule.startTime)} - ${_formatTime(schedule.endTime)}',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
schedule.isRecurring ? _weekdayLabel(schedule.weekdays) : 'Eenmalige dienst',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed: onTap,
|
|
||||||
icon: const Icon(Icons.list_alt, size: 18),
|
|
||||||
label: const Text('Haltes'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InfoChip extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
const _InfoChip({required this.label});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatTime(DateTime time) {
|
|
||||||
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
String _weekdayLabel(List<int> weekdays) {
|
|
||||||
const labels = ['Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za'];
|
|
||||||
if (weekdays.isEmpty) {
|
|
||||||
return 'Eenmalige dienst';
|
|
||||||
}
|
|
||||||
if (weekdays.length == 7) {
|
|
||||||
return 'Dagelijks';
|
|
||||||
}
|
|
||||||
return weekdays.where((day) => day >= 0 && day < labels.length).map((day) => labels[day]).join(', ');
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'auth_service.dart';
|
|
||||||
|
|
||||||
/// Global navigator and messenger keys so services can navigate/logout
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
||||||
final GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
|
|
||||||
|
|
||||||
/// Handle expired authentication: clear stored token and navigate to login.
|
|
||||||
Future<void> handleAuthExpired({String? message}) async {
|
|
||||||
try {
|
|
||||||
await AuthService().logout();
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
// Show a message if possible
|
|
||||||
try {
|
|
||||||
final messenger = messengerKey.currentState;
|
|
||||||
if (messenger != null && message != null) {
|
|
||||||
messenger.showSnackBar(SnackBar(content: Text(message)));
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
// Navigate to login, removing all previous routes
|
|
||||||
try {
|
|
||||||
final nav = navigatorKey.currentState;
|
|
||||||
if (nav != null) {
|
|
||||||
nav.pushNamedAndRemoveUntil('/login', (r) => false);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import '../models/user.dart';
|
|
||||||
import 'error_log_service.dart';
|
|
||||||
|
|
||||||
/// Authentication Service
|
|
||||||
class AuthService {
|
|
||||||
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
|
|
||||||
static const String _tokenKey = 'auth_token';
|
|
||||||
|
|
||||||
late SharedPreferences _prefs;
|
|
||||||
|
|
||||||
Future<void> _reportError(String partOfService, Object error) {
|
|
||||||
return ErrorLogService.report(
|
|
||||||
service: 'bus_terminal',
|
|
||||||
partOfService: partOfService,
|
|
||||||
error: error.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initPrefs() async {
|
|
||||||
_prefs = await SharedPreferences.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Login with userCode and password
|
|
||||||
Future<AuthResponse> login({
|
|
||||||
required int userCode,
|
|
||||||
required String password,
|
|
||||||
}) async {
|
|
||||||
await _initPrefs();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse('$_apiBaseUrl/login'),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: jsonEncode({
|
|
||||||
'userCode': userCode,
|
|
||||||
'password': password,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final body = response.body;
|
|
||||||
try {
|
|
||||||
final data = jsonDecode(body) as Map<String, dynamic>;
|
|
||||||
final authResponse = AuthResponse.fromJson(data);
|
|
||||||
|
|
||||||
// Store token
|
|
||||||
await _prefs.setString(_tokenKey, authResponse.token);
|
|
||||||
|
|
||||||
return authResponse;
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('AuthService.login.parse', e);
|
|
||||||
// Surface the response body to help debug backend field names / formats
|
|
||||||
throw Exception('Login parse error: $e -- response body: $body');
|
|
||||||
}
|
|
||||||
} else if (response.statusCode == 401) {
|
|
||||||
throw Exception('Invalid credentials');
|
|
||||||
} else {
|
|
||||||
throw Exception('Login failed: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('AuthService.login', e);
|
|
||||||
throw Exception('Login error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get stored token
|
|
||||||
Future<String?> getToken() async {
|
|
||||||
await _initPrefs();
|
|
||||||
return _prefs.getString(_tokenKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if user is logged in
|
|
||||||
Future<bool> isLoggedIn() async {
|
|
||||||
final token = await getToken();
|
|
||||||
return token != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Logout
|
|
||||||
Future<void> logout() async {
|
|
||||||
await _initPrefs();
|
|
||||||
await _prefs.remove(_tokenKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get Authorization header
|
|
||||||
Future<Map<String, String>> getAuthHeaders() async {
|
|
||||||
final token = await getToken();
|
|
||||||
return {
|
|
||||||
'Authorization': 'Bearer $token',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
class ErrorLogService {
|
|
||||||
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
|
|
||||||
|
|
||||||
static Future<void> report({
|
|
||||||
required String service,
|
|
||||||
required String partOfService,
|
|
||||||
required String error,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
await http.post(
|
|
||||||
Uri.parse('$_apiBaseUrl/error_log'),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: jsonEncode({
|
|
||||||
'service': service,
|
|
||||||
'partOfService': partOfService,
|
|
||||||
'error': error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
// Intentionally ignore logging failures.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Custom exception for authentication errors
|
|
||||||
import 'app_navigator.dart';
|
|
||||||
|
|
||||||
class UnauthorizedException implements Exception {
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
UnauthorizedException(this.message) {
|
|
||||||
// Trigger centralized logout/navigation asynchronously
|
|
||||||
try {
|
|
||||||
handleAuthExpired(message: message);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => message;
|
|
||||||
}
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'error_log_service.dart';
|
|
||||||
import '../models/navigation.dart';
|
|
||||||
|
|
||||||
/// Navigation Service using OpenStreetMap
|
|
||||||
class NavigationService {
|
|
||||||
// OSRM (OpenStreetMap Routing Machine) - free public service
|
|
||||||
static const String _osrmBaseUrl = 'https://router.project-osrm.org/route/v1/driving';
|
|
||||||
|
|
||||||
/// Get current device location
|
|
||||||
Future<void> _reportError(String partOfService, Object error) {
|
|
||||||
return ErrorLogService.report(
|
|
||||||
service: 'bus_terminal',
|
|
||||||
partOfService: partOfService,
|
|
||||||
error: error.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Future<NavigationPoint?> getCurrentLocation() async {
|
|
||||||
try {
|
|
||||||
final permission = await Geolocator.checkPermission();
|
|
||||||
if (permission == LocationPermission.denied) {
|
|
||||||
await Geolocator.requestPermission();
|
|
||||||
}
|
|
||||||
|
|
||||||
final position = await Geolocator.getCurrentPosition(
|
|
||||||
desiredAccuracy: LocationAccuracy.high,
|
|
||||||
);
|
|
||||||
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: position.latitude,
|
|
||||||
longitude: position.longitude,
|
|
||||||
name: 'Current Location',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('NavigationService.getCurrentLocation', e);
|
|
||||||
throw Exception('Failed to get location: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get route between two points
|
|
||||||
/// Uses OSRM for open routing without API key
|
|
||||||
Future<NavigationRoute> getRoute({
|
|
||||||
required NavigationPoint start,
|
|
||||||
required NavigationPoint end,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
bool validCoord(double lat, double lon) {
|
|
||||||
return lat.isFinite && lon.isFinite && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validCoord(start.latitude, start.longitude) || !validCoord(end.latitude, end.longitude)) {
|
|
||||||
throw Exception('Invalid coordinates for routing: start=(${start.latitude},${start.longitude}) end=(${end.latitude},${end.longitude})');
|
|
||||||
}
|
|
||||||
// OSRM format: /route/v1/driving/lon1,lat1;lon2,lat2
|
|
||||||
final coordinates =
|
|
||||||
'${start.longitude},${start.latitude};${end.longitude},${end.latitude}';
|
|
||||||
final url =
|
|
||||||
'$_osrmBaseUrl/$coordinates?overview=full&steps=true&geometries=geojson';
|
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>?;
|
|
||||||
if (data == null) {
|
|
||||||
throw Exception('Empty response from routing service');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['code'] != 'Ok') {
|
|
||||||
throw Exception('Route not found: ${data['message'] ?? 'Unknown error'}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routes = data['routes'] as List?;
|
|
||||||
if (routes == null || routes.isEmpty) {
|
|
||||||
throw Exception('No route found in response');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routeData = routes.first as Map<String, dynamic>;
|
|
||||||
return NavigationRoute.fromOsrmRouteData(
|
|
||||||
routeData,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
);
|
|
||||||
} else if (response.statusCode == 400) {
|
|
||||||
// Bad request: OSRM couldn't parse coordinates. Fall back to a simple straight-line route.
|
|
||||||
try {
|
|
||||||
final distanceMeters = calculateDistance(start, end);
|
|
||||||
final durationSec = (distanceMeters / 11.111111).toInt(); // assume ~40 km/h -> 11.11 m/s
|
|
||||||
final leg = RouteLeg.fromPoints(points: [start, end], distance: distanceMeters, duration: durationSec, summary: 'Direct');
|
|
||||||
return NavigationRoute(start: start, end: end, legs: [leg], totalDistance: distanceMeters, totalDuration: durationSec);
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Failed to get route (400) and fallback failed: ${e.toString()}');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to get route: ${response.statusCode} -- response body: ${response.body}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('NavigationService.getRoute', e);
|
|
||||||
throw Exception('Navigation error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a route that passes through the provided stops in the given order.
|
|
||||||
Future<NavigationRoute> getRouteThroughStops({
|
|
||||||
required List<NavigationPoint> stops,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
if (stops.length < 2) {
|
|
||||||
throw Exception('At least two stops are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
final coordinates = stops
|
|
||||||
.map((stop) => '${stop.longitude},${stop.latitude}')
|
|
||||||
.join(';');
|
|
||||||
final url =
|
|
||||||
'$_osrmBaseUrl/$coordinates?overview=full&steps=true&geometries=geojson&source=first&destination=last';
|
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
if (response.statusCode == 400) {
|
|
||||||
final distanceMeters = _sumDistancesBetween(stops);
|
|
||||||
final durationSec = (distanceMeters / 11.111111).toInt();
|
|
||||||
final leg = RouteLeg.fromPoints(
|
|
||||||
points: stops,
|
|
||||||
distance: distanceMeters,
|
|
||||||
duration: durationSec,
|
|
||||||
summary: 'Direct fallback',
|
|
||||||
);
|
|
||||||
return NavigationRoute(
|
|
||||||
start: stops.first,
|
|
||||||
end: stops.last,
|
|
||||||
legs: [leg],
|
|
||||||
totalDistance: distanceMeters,
|
|
||||||
totalDuration: durationSec,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception('Failed to get route: ${response.statusCode} -- response body: ${response.body}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>?;
|
|
||||||
if (data == null) {
|
|
||||||
throw Exception('Empty response from routing service');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['code'] != 'Ok') {
|
|
||||||
throw Exception('Route not found: ${data['message'] ?? 'Unknown error'}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routes = data['routes'] as List?;
|
|
||||||
if (routes == null || routes.isEmpty) {
|
|
||||||
throw Exception('No route found in response');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routeData = routes.first as Map<String, dynamic>;
|
|
||||||
return NavigationRoute.fromOsrmRouteData(
|
|
||||||
routeData,
|
|
||||||
start: stops.first,
|
|
||||||
end: stops.last,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('NavigationService.getRouteThroughStops', e);
|
|
||||||
throw Exception('Navigation error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an optimized trip through multiple stops.
|
|
||||||
/// Keeps the first and last stops fixed and lets OSRM order the intermediates.
|
|
||||||
Future<OptimizedRouteResult> getOptimizedRouteThroughStops({
|
|
||||||
required List<NavigationPoint> stops,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
if (stops.length < 2) {
|
|
||||||
throw Exception('At least two stops are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
final coordinates = stops
|
|
||||||
.map((stop) => '${stop.longitude},${stop.latitude}')
|
|
||||||
.join(';');
|
|
||||||
final url =
|
|
||||||
'https://router.project-osrm.org/trip/v1/driving/$coordinates?overview=full&steps=true&geometries=geojson&source=first&destination=last&roundtrip=false';
|
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
if (response.statusCode == 400) {
|
|
||||||
// fallback: return simple ordered stops route
|
|
||||||
final distanceMeters = _sumDistancesBetween(stops);
|
|
||||||
final durationSec = (distanceMeters / 11.111111).toInt();
|
|
||||||
final leg = RouteLeg.fromPoints(points: stops, distance: distanceMeters, duration: durationSec, summary: 'Fallback optimized trip');
|
|
||||||
final route = NavigationRoute(start: stops.first, end: stops.last, legs: [leg], totalDistance: distanceMeters, totalDuration: durationSec);
|
|
||||||
return OptimizedRouteResult(route: route, orderedStops: stops);
|
|
||||||
}
|
|
||||||
throw Exception('Failed to get optimized route: ${response.statusCode} -- response body: ${response.body}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>?;
|
|
||||||
if (data == null) {
|
|
||||||
throw Exception('Empty response from routing service');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['code'] != 'Ok') {
|
|
||||||
throw Exception('Optimized route not found: ${data['message'] ?? 'Unknown error'}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final trips = data['trips'] as List?;
|
|
||||||
if (trips == null || trips.isEmpty) {
|
|
||||||
throw Exception('No optimized trip found');
|
|
||||||
}
|
|
||||||
|
|
||||||
final trip = trips.first as Map<String, dynamic>;
|
|
||||||
final geometry = trip['geometry'] as Map<String, dynamic>?;
|
|
||||||
final coordinatesData = geometry?['coordinates'] as List? ?? [];
|
|
||||||
final points = coordinatesData
|
|
||||||
.map((coord) {
|
|
||||||
if (coord is List && coord.length >= 2) {
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: (coord[1] as num).toDouble(),
|
|
||||||
longitude: (coord[0] as num).toDouble(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.whereType<NavigationPoint>()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final waypointItems = (data['waypoints'] as List?) ?? [];
|
|
||||||
final orderedPairs = <MapEntry<int, NavigationPoint>>[];
|
|
||||||
|
|
||||||
for (var i = 0; i < waypointItems.length && i < stops.length; i++) {
|
|
||||||
final waypoint = waypointItems[i];
|
|
||||||
if (waypoint is! Map<String, dynamic>) continue;
|
|
||||||
final index = waypoint['waypoint_index'] as num?;
|
|
||||||
if (index == null) continue;
|
|
||||||
orderedPairs.add(MapEntry(index.toInt(), stops[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
orderedPairs.sort((a, b) => a.key.compareTo(b.key));
|
|
||||||
final orderedStops = orderedPairs.isNotEmpty
|
|
||||||
? orderedPairs.map((entry) => entry.value).toList()
|
|
||||||
: stops;
|
|
||||||
|
|
||||||
final route = NavigationRoute(
|
|
||||||
start: orderedStops.first,
|
|
||||||
end: orderedStops.last,
|
|
||||||
legs: [
|
|
||||||
RouteLeg.fromPoints(
|
|
||||||
points: points,
|
|
||||||
distance: (trip['distance'] as num?)?.toDouble() ?? 0.0,
|
|
||||||
duration: (trip['duration'] as num?)?.toInt() ?? 0,
|
|
||||||
summary: 'Optimized trip',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
totalDistance: (trip['distance'] as num?)?.toDouble() ?? 0.0,
|
|
||||||
totalDuration: (trip['duration'] as num?)?.toInt() ?? 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return OptimizedRouteResult(
|
|
||||||
route: route,
|
|
||||||
orderedStops: orderedStops.isNotEmpty ? orderedStops : stops,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('NavigationService.getOptimizedRouteThroughStops', e);
|
|
||||||
throw Exception('Navigation error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get multiple routes (alternatives)
|
|
||||||
Future<List<NavigationRoute>> getMultipleRoutes({
|
|
||||||
required NavigationPoint start,
|
|
||||||
required NavigationPoint end,
|
|
||||||
int alternatives = 2,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final coordinates =
|
|
||||||
'${start.longitude},${start.latitude};${end.longitude},${end.latitude}';
|
|
||||||
final url =
|
|
||||||
'$_osrmBaseUrl/$coordinates?overview=full&steps=true&geometries=geojson&alternatives=$alternatives';
|
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>?;
|
|
||||||
if (data == null) {
|
|
||||||
throw Exception('Empty response from routing service');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['code'] != 'Ok') {
|
|
||||||
throw Exception('Routes not found: ${data['message'] ?? 'Unknown error'}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routesData = data['routes'] as List?;
|
|
||||||
if (routesData == null || routesData.isEmpty) {
|
|
||||||
throw Exception('No routes found in response');
|
|
||||||
}
|
|
||||||
|
|
||||||
final routes = routesData
|
|
||||||
.map((routeData) {
|
|
||||||
if (routeData is! Map<String, dynamic>) return null;
|
|
||||||
return NavigationRoute.fromOsrmRouteData(
|
|
||||||
routeData,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.whereType<NavigationRoute>()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return routes;
|
|
||||||
} else if (response.statusCode == 400) {
|
|
||||||
// fallback: single straight-line route
|
|
||||||
final distanceMeters = calculateDistance(start, end);
|
|
||||||
final durationSec = (distanceMeters / 11.111111).toInt();
|
|
||||||
final leg = RouteLeg.fromPoints(points: [start, end], distance: distanceMeters, duration: durationSec, summary: 'Direct');
|
|
||||||
return [NavigationRoute(start: start, end: end, legs: [leg], totalDistance: distanceMeters, totalDuration: durationSec)];
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to get routes: ${response.statusCode} -- response body: ${response.body}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('NavigationService.getMultipleRoutes', e);
|
|
||||||
throw Exception('Navigation error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _sumDistancesBetween(List<NavigationPoint> points) {
|
|
||||||
var total = 0.0;
|
|
||||||
for (var i = 0; i < points.length - 1; i++) {
|
|
||||||
total += calculateDistance(points[i], points[i + 1]);
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate straight-line distance between two points
|
|
||||||
double calculateDistance(NavigationPoint p1, NavigationPoint p2) {
|
|
||||||
const distance = Distance();
|
|
||||||
return distance(
|
|
||||||
LatLng(p1.latitude, p1.longitude),
|
|
||||||
LatLng(p2.latitude, p2.longitude),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Geocode a place name to coordinates using Nominatim (OpenStreetMap)
|
|
||||||
Future<NavigationPoint> geocodePlace(String name) async {
|
|
||||||
try {
|
|
||||||
final uri = Uri.https('nominatim.openstreetmap.org', '/search', {
|
|
||||||
'q': name,
|
|
||||||
'format': 'json',
|
|
||||||
'limit': '1',
|
|
||||||
});
|
|
||||||
|
|
||||||
final response = await http.get(uri, headers: {
|
|
||||||
'User-Agent': 'bus-terminal-app',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw Exception('Geocoding failed: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final list = jsonDecode(response.body) as List<dynamic>?;
|
|
||||||
if (list == null || list.isEmpty) {
|
|
||||||
throw Exception('No geocoding result for "$name"');
|
|
||||||
}
|
|
||||||
|
|
||||||
final item = list[0] as Map<String, dynamic>;
|
|
||||||
final lat = double.parse(item['lat'] as String);
|
|
||||||
final lon = double.parse(item['lon'] as String);
|
|
||||||
final display = item['display_name'] as String? ?? name;
|
|
||||||
|
|
||||||
return NavigationPoint(latitude: lat, longitude: lon, name: display);
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('NavigationService.geocodePlace', e);
|
|
||||||
throw Exception('Geocode error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import '../models/schedule.dart';
|
|
||||||
import '../models/navigation.dart';
|
|
||||||
import 'auth_service.dart';
|
|
||||||
import 'error_log_service.dart';
|
|
||||||
import 'exceptions.dart';
|
|
||||||
|
|
||||||
/// Schedule/Route Service
|
|
||||||
class ScheduleService {
|
|
||||||
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
|
|
||||||
final _authService = AuthService();
|
|
||||||
|
|
||||||
Future<void> _reportError(String partOfService, Object error) {
|
|
||||||
return ErrorLogService.report(
|
|
||||||
service: 'bus_terminal',
|
|
||||||
partOfService: partOfService,
|
|
||||||
error: error.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get public schedule overview for a specific date.
|
|
||||||
Future<List<ServiceSchedule>> getSchedules(DateTime date, {int? lineId}) async {
|
|
||||||
try {
|
|
||||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
||||||
final queryParameters = <String, String>{'date': dateStr};
|
|
||||||
if (lineId != null) {
|
|
||||||
queryParameters['lineId'] = lineId.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
final uri = Uri.parse('$_apiBaseUrl/schedules/overview').replace(queryParameters: queryParameters);
|
|
||||||
final response = await http.get(uri);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
return (data['schedules'] as List? ?? [])
|
|
||||||
.map((schedule) => schedule as Map<String, dynamic>)
|
|
||||||
.map(ServiceSchedule.fromJson)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception('Failed to load schedules: ${response.statusCode}');
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('ScheduleService.getSchedules', e);
|
|
||||||
throw Exception('Schedule error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all recurring schedule templates, independent of a specific date.
|
|
||||||
Future<List<ServiceSchedule>> getRecurringSchedules({int? lineId}) async {
|
|
||||||
try {
|
|
||||||
final queryParameters = <String, String>{};
|
|
||||||
if (lineId != null) {
|
|
||||||
queryParameters['lineId'] = lineId.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
final uri = Uri.parse('$_apiBaseUrl/schedules/recurring').replace(queryParameters: queryParameters);
|
|
||||||
final response = await http.get(uri);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
return (data['schedules'] as List? ?? [])
|
|
||||||
.map((schedule) => schedule as Map<String, dynamic>)
|
|
||||||
.map(ServiceSchedule.fromJson)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception('Failed to load recurring schedules: ${response.statusCode}');
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('ScheduleService.getRecurringSchedules', e);
|
|
||||||
throw Exception('Recurring schedule error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get stop list for a specific schedule.
|
|
||||||
Future<List<ScheduleStop>> getScheduleStops(int scheduleId) async {
|
|
||||||
try {
|
|
||||||
final response = await http.get(Uri.parse('$_apiBaseUrl/schedules/$scheduleId/stops'));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
return (data['stops'] as List? ?? [])
|
|
||||||
.map((stop) => stop as Map<String, dynamic>)
|
|
||||||
.map(ScheduleStop.fromJson)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception('Failed to load schedule stops: ${response.statusCode}');
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('ScheduleService.getScheduleStops', e);
|
|
||||||
throw Exception('Schedule stops error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get daily schedule for a specific date (includes recurring schedules)
|
|
||||||
Future<DailySchedule> getDailySchedule(DateTime date) async {
|
|
||||||
try {
|
|
||||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
// Call /schedules/overview which includes both one-time and recurring schedules for the date
|
|
||||||
final uri = Uri.parse('$_apiBaseUrl/schedules/overview').replace(
|
|
||||||
queryParameters: {'date': dateStr}
|
|
||||||
);
|
|
||||||
final response = await http.get(uri);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
final schedules = (data['schedules'] as List? ?? [])
|
|
||||||
.map((s) => s as Map<String, dynamic>)
|
|
||||||
.map(ServiceSchedule.fromJson)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Convert ServiceSchedule to Route objects for display
|
|
||||||
final routes = schedules.map((schedule) {
|
|
||||||
return Route(
|
|
||||||
id: schedule.id,
|
|
||||||
busLine: BusLine(
|
|
||||||
id: schedule.busLineId,
|
|
||||||
lineNumber: schedule.lineNumber,
|
|
||||||
startStop: '',
|
|
||||||
endStop: '',
|
|
||||||
estimatedDuration: 60,
|
|
||||||
description: '',
|
|
||||||
),
|
|
||||||
busType: BusType(
|
|
||||||
id: schedule.busId,
|
|
||||||
name: schedule.busName,
|
|
||||||
seatCapacity: 0,
|
|
||||||
licensePlate: '',
|
|
||||||
),
|
|
||||||
startTime: schedule.startTime,
|
|
||||||
endTime: schedule.endTime,
|
|
||||||
status: schedule.status,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return DailySchedule(
|
|
||||||
routes: routes,
|
|
||||||
date: date,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to load schedule: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('ScheduleService.getDailySchedule', e);
|
|
||||||
throw Exception('Schedule error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get today's schedule
|
|
||||||
Future<DailySchedule> getTodaySchedule() async {
|
|
||||||
return getDailySchedule(DateTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get per-date progress for a schedule occurrence (public endpoint)
|
|
||||||
Future<Map<String, dynamic>> getScheduleProgress(int scheduleId, DateTime date) async {
|
|
||||||
try {
|
|
||||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
||||||
final uri = Uri.parse('$_apiBaseUrl/public/schedules/$scheduleId/progress').replace(queryParameters: {'date': dateStr});
|
|
||||||
final response = await http.get(uri);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception('Failed to load progress: ${response.statusCode}');
|
|
||||||
} catch (e) {
|
|
||||||
await _reportError('ScheduleService.getScheduleProgress', e);
|
|
||||||
throw Exception('Schedule progress error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get route details
|
|
||||||
Future<Route> getRouteDetails(int routeId) async {
|
|
||||||
try {
|
|
||||||
final headers = await _authService.getAuthHeaders();
|
|
||||||
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse('$_apiBaseUrl/routes/$routeId'),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
return Route.fromJson(data);
|
|
||||||
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
|
||||||
throw UnauthorizedException('Session expired - Please login again');
|
|
||||||
} else {
|
|
||||||
final body = response.body;
|
|
||||||
throw Exception('Failed to load route: ${response.statusCode} -- response body: $body');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e is UnauthorizedException) rethrow;
|
|
||||||
await _reportError('ScheduleService.getRouteDetails', e);
|
|
||||||
throw Exception('Route error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update route status
|
|
||||||
Future<void> updateRouteStatus(int routeId, String status) async {
|
|
||||||
try {
|
|
||||||
final headers = await _authService.getAuthHeaders();
|
|
||||||
|
|
||||||
// Transit API updates status at /routes/:routeId/status
|
|
||||||
final response = await http.patch(
|
|
||||||
Uri.parse('$_apiBaseUrl/routes/$routeId/status'),
|
|
||||||
headers: headers,
|
|
||||||
body: jsonEncode({'status': status}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
|
||||||
throw UnauthorizedException('Session expired - Please login again');
|
|
||||||
} else if (response.statusCode != 200) {
|
|
||||||
throw Exception('Failed to update status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e is UnauthorizedException) rethrow;
|
|
||||||
await _reportError('ScheduleService.updateRouteStatus', e);
|
|
||||||
throw Exception('Status update error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update schedule status for driver-facing schedule workflows.
|
|
||||||
Future<void> updateScheduleStatus(int scheduleId, String status, {DateTime? date}) async {
|
|
||||||
try {
|
|
||||||
final headers = await _authService.getAuthHeaders();
|
|
||||||
|
|
||||||
final body = <String, dynamic>{'status': status};
|
|
||||||
if (date != null) {
|
|
||||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
||||||
body['date'] = dateStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await http.patch(
|
|
||||||
Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/status'),
|
|
||||||
headers: headers,
|
|
||||||
body: jsonEncode(body),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
|
||||||
throw UnauthorizedException('Session expired - Please login again');
|
|
||||||
} else if (response.statusCode != 200) {
|
|
||||||
throw Exception('Failed to update schedule status: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e is UnauthorizedException) rethrow;
|
|
||||||
await _reportError('ScheduleService.updateScheduleStatus', e);
|
|
||||||
throw Exception('Schedule status update error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get stops for a bus line
|
|
||||||
Future<List<NavigationPoint>> getStops(int busLineId) async {
|
|
||||||
try {
|
|
||||||
final headers = await _authService.getAuthHeaders();
|
|
||||||
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse('$_apiBaseUrl/bus-lines/$busLineId/stops'),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
final raw = data['stops'] as List? ?? [];
|
|
||||||
final stops = raw.map((s) => s as Map<String, dynamic>).map((s) {
|
|
||||||
final lat = (s['latitude'] ?? s['lat']) as num?;
|
|
||||||
final lon = (s['longitude'] ?? s['lon']) as num?;
|
|
||||||
final name = (s['name'] ?? s['stop_name'] ?? s['stopName']) as String?;
|
|
||||||
if (lat == null || lon == null) return null;
|
|
||||||
return NavigationPoint(
|
|
||||||
latitude: lat.toDouble(),
|
|
||||||
longitude: lon.toDouble(),
|
|
||||||
name: name,
|
|
||||||
);
|
|
||||||
}).whereType<NavigationPoint>().toList();
|
|
||||||
|
|
||||||
return stops;
|
|
||||||
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
|
||||||
throw UnauthorizedException('Session expired - Please login again');
|
|
||||||
} else {
|
|
||||||
final body = response.body;
|
|
||||||
throw Exception('Failed to load stops: ${response.statusCode} -- response body: $body');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e is UnauthorizedException) rethrow;
|
|
||||||
await _reportError('ScheduleService.getStops', e);
|
|
||||||
throw Exception('Get stops error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark a schedule stop as passed by the driver.
|
|
||||||
Future<void> markScheduleStopPassed({
|
|
||||||
required int scheduleId,
|
|
||||||
required int stopOrder,
|
|
||||||
DateTime? actualPassedAt,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final headers = await _authService.getAuthHeaders();
|
|
||||||
|
|
||||||
final body = <String, dynamic>{};
|
|
||||||
if (actualPassedAt != null) body['actualPassedAt'] = actualPassedAt.toIso8601String();
|
|
||||||
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/stops/$stopOrder/passed'),
|
|
||||||
headers: headers,
|
|
||||||
body: jsonEncode(body),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
|
||||||
await _authService.logout();
|
|
||||||
throw UnauthorizedException('Session expired - Please login again');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
||||||
throw Exception('Failed to mark stop as passed: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e is UnauthorizedException) rethrow;
|
|
||||||
await _reportError('ScheduleService.markScheduleStopPassed', e);
|
|
||||||
throw Exception('Mark stop passed error: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Material 3 Theme configuratie voor bus_terminal
|
|
||||||
class AppTheme {
|
|
||||||
static const Color _navy = Color(0xFF0F172A);
|
|
||||||
static const Color _surface = Color(0xFFF8FAFC);
|
|
||||||
static const Color _surfaceDark = Color(0xFF111827);
|
|
||||||
static const Color _amber = Color(0xFFF59E0B);
|
|
||||||
static const Color _cyan = Color(0xFF38BDF8);
|
|
||||||
static const Color _success = Color(0xFF22C55E);
|
|
||||||
static const Color _error = Color(0xFFEF4444);
|
|
||||||
|
|
||||||
static TextTheme _textTheme(ColorScheme colorScheme) {
|
|
||||||
return TextTheme(
|
|
||||||
displayLarge: const TextStyle(fontSize: 46, fontWeight: FontWeight.w800, letterSpacing: -1.2),
|
|
||||||
displayMedium: const TextStyle(fontSize: 38, fontWeight: FontWeight.w800, letterSpacing: -0.9),
|
|
||||||
headlineLarge: const TextStyle(fontSize: 30, fontWeight: FontWeight.w800, letterSpacing: -0.4),
|
|
||||||
headlineMedium: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, letterSpacing: -0.3),
|
|
||||||
titleLarge: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
|
|
||||||
titleMedium: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
|
|
||||||
titleSmall: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
|
|
||||||
bodyLarge: TextStyle(fontSize: 16, height: 1.45, color: colorScheme.onSurface),
|
|
||||||
bodyMedium: TextStyle(fontSize: 14, height: 1.45, color: colorScheme.onSurfaceVariant),
|
|
||||||
bodySmall: TextStyle(fontSize: 12, height: 1.35, color: colorScheme.onSurfaceVariant),
|
|
||||||
labelLarge: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
|
|
||||||
labelMedium: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
|
||||||
labelSmall: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 0.25),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ColorScheme _buildLightScheme() {
|
|
||||||
return ColorScheme.fromSeed(
|
|
||||||
seedColor: _cyan,
|
|
||||||
brightness: Brightness.light,
|
|
||||||
primary: _navy,
|
|
||||||
secondary: _amber,
|
|
||||||
tertiary: _cyan,
|
|
||||||
surface: _surface,
|
|
||||||
onSurface: _navy,
|
|
||||||
error: _error,
|
|
||||||
).copyWith(
|
|
||||||
surfaceContainerHighest: const Color(0xFFE2E8F0),
|
|
||||||
outline: const Color(0xFF94A3B8),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ColorScheme _buildDarkScheme() {
|
|
||||||
return ColorScheme.fromSeed(
|
|
||||||
seedColor: _cyan,
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
primary: _cyan,
|
|
||||||
secondary: _amber,
|
|
||||||
tertiary: _success,
|
|
||||||
surface: _surfaceDark,
|
|
||||||
onSurface: Colors.white,
|
|
||||||
error: _error,
|
|
||||||
).copyWith(
|
|
||||||
surfaceContainerHighest: const Color(0xFF1F2937),
|
|
||||||
outline: const Color(0xFF334155),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ThemeData lightTheme() {
|
|
||||||
final colorScheme = _buildLightScheme();
|
|
||||||
return ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
brightness: Brightness.light,
|
|
||||||
colorScheme: colorScheme,
|
|
||||||
scaffoldBackgroundColor: const Color(0xFFF1F5F9),
|
|
||||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
||||||
textTheme: _textTheme(colorScheme),
|
|
||||||
appBarTheme: AppBarTheme(
|
|
||||||
backgroundColor: colorScheme.surface,
|
|
||||||
foregroundColor: colorScheme.onSurface,
|
|
||||||
elevation: 0,
|
|
||||||
centerTitle: false,
|
|
||||||
titleTextStyle: _textTheme(colorScheme).titleLarge?.copyWith(color: colorScheme.onSurface),
|
|
||||||
),
|
|
||||||
cardTheme: CardThemeData(
|
|
||||||
color: colorScheme.surface,
|
|
||||||
elevation: 0,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
dividerTheme: DividerThemeData(color: colorScheme.outline.withOpacity(0.18), thickness: 1),
|
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white,
|
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: colorScheme.outline.withOpacity(0.25))),
|
|
||||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: colorScheme.outline.withOpacity(0.25))),
|
|
||||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: colorScheme.primary, width: 2)),
|
|
||||||
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: colorScheme.error)),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
|
||||||
labelStyle: TextStyle(color: colorScheme.onSurfaceVariant),
|
|
||||||
hintStyle: TextStyle(color: colorScheme.onSurfaceVariant.withOpacity(0.8)),
|
|
||||||
),
|
|
||||||
filledButtonTheme: FilledButtonThemeData(
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.primary,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
minimumSize: const Size.fromHeight(56),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.secondary,
|
|
||||||
foregroundColor: colorScheme.onSecondary,
|
|
||||||
minimumSize: const Size.fromHeight(52),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: colorScheme.onSurface,
|
|
||||||
minimumSize: const Size.fromHeight(52),
|
|
||||||
side: BorderSide(color: colorScheme.outline.withOpacity(0.35)),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
chipTheme: ChipThemeData(
|
|
||||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
|
||||||
labelStyle: TextStyle(color: colorScheme.onSurface, fontWeight: FontWeight.w600),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
|
|
||||||
),
|
|
||||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
|
||||||
color: colorScheme.primary,
|
|
||||||
linearTrackColor: colorScheme.primary.withOpacity(0.12),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ThemeData darkTheme() {
|
|
||||||
final colorScheme = _buildDarkScheme();
|
|
||||||
return ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
colorScheme: colorScheme,
|
|
||||||
scaffoldBackgroundColor: const Color(0xFF0B1220),
|
|
||||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
||||||
textTheme: _textTheme(colorScheme).apply(bodyColor: Colors.white, displayColor: Colors.white),
|
|
||||||
appBarTheme: AppBarTheme(
|
|
||||||
backgroundColor: const Color(0xFF0B1220),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
centerTitle: false,
|
|
||||||
titleTextStyle: _textTheme(colorScheme).titleLarge?.copyWith(color: Colors.white),
|
|
||||||
),
|
|
||||||
cardTheme: CardThemeData(
|
|
||||||
color: const Color(0xFF111827),
|
|
||||||
elevation: 0,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
dividerTheme: const DividerThemeData(color: Color(0xFF243244), thickness: 1),
|
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
|
||||||
filled: true,
|
|
||||||
fillColor: const Color(0xFF111827),
|
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF334155))),
|
|
||||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF334155))),
|
|
||||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: colorScheme.primary, width: 2)),
|
|
||||||
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: colorScheme.error)),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
|
||||||
labelStyle: const TextStyle(color: Color(0xFFCBD5E1)),
|
|
||||||
hintStyle: const TextStyle(color: Color(0xFF94A3B8)),
|
|
||||||
),
|
|
||||||
filledButtonTheme: FilledButtonThemeData(
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.primary,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
minimumSize: const Size.fromHeight(56),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.secondary,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
minimumSize: const Size.fromHeight(52),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
minimumSize: const Size.fromHeight(52),
|
|
||||||
side: const BorderSide(color: Color(0xFF334155)),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
chipTheme: ChipThemeData(
|
|
||||||
backgroundColor: const Color(0xFF1E293B),
|
|
||||||
labelStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
|
|
||||||
),
|
|
||||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
|
||||||
color: colorScheme.primary,
|
|
||||||
linearTrackColor: colorScheme.primary.withOpacity(0.16),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Custom input field optimized for bus drivers
|
|
||||||
/// - Large touch targets (40+ height)
|
|
||||||
/// - Clear visibility
|
|
||||||
/// - Accessible design
|
|
||||||
class CustomInputField extends StatefulWidget {
|
|
||||||
final String label;
|
|
||||||
final String hint;
|
|
||||||
final TextEditingController controller;
|
|
||||||
final bool obscureText;
|
|
||||||
final TextInputType keyboardType;
|
|
||||||
final String? Function(String?)? validator;
|
|
||||||
|
|
||||||
const CustomInputField({
|
|
||||||
super.key,
|
|
||||||
required this.label,
|
|
||||||
required this.hint,
|
|
||||||
required this.controller,
|
|
||||||
this.obscureText = false,
|
|
||||||
this.keyboardType = TextInputType.text,
|
|
||||||
this.validator,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CustomInputField> createState() => _CustomInputFieldState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CustomInputFieldState extends State<CustomInputField> {
|
|
||||||
late bool _obscureText;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_obscureText = widget.obscureText;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.label,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
TextFormField(
|
|
||||||
controller: widget.controller,
|
|
||||||
obscureText: _obscureText,
|
|
||||||
keyboardType: widget.keyboardType,
|
|
||||||
validator: widget.validator,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: widget.hint,
|
|
||||||
hintStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16, // Larger vertical padding for touch targets
|
|
||||||
),
|
|
||||||
prefixIcon: Icon(
|
|
||||||
widget.obscureText ? Icons.lock_outline : Icons.badge_outlined,
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
filled: true,
|
|
||||||
fillColor: colorScheme.surface,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
borderSide: BorderSide(color: colorScheme.outline.withOpacity(0.25)),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
borderSide: BorderSide(color: colorScheme.outline.withOpacity(0.25)),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
borderSide: BorderSide(color: colorScheme.primary, width: 2),
|
|
||||||
),
|
|
||||||
suffixIcon: widget.obscureText
|
|
||||||
? IconButton(
|
|
||||||
tooltip: _obscureText ? 'Toon wachtwoord' : 'Verberg wachtwoord',
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _obscureText = !_obscureText);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
_obscureText ? Icons.visibility_off : Icons.visibility,
|
|
||||||
size: 28, // Larger icon for accessibility
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
splashRadius: 24,
|
|
||||||
constraints: const BoxConstraints(minWidth: 48, minHeight: 48),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../models/schedule.dart' as schedule;
|
|
||||||
import '../screens/navigation_screen.dart';
|
|
||||||
import '../services/schedule_service.dart';
|
|
||||||
|
|
||||||
/// Route card widget showing route details
|
|
||||||
class RouteCard extends StatelessWidget {
|
|
||||||
final schedule.Route route;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
const RouteCard({
|
|
||||||
super.key,
|
|
||||||
required this.route,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
Color _getStatusColor(String status) {
|
|
||||||
switch (status) {
|
|
||||||
case 'scheduled':
|
|
||||||
return Colors.blue;
|
|
||||||
case 'in_progress':
|
|
||||||
return Colors.orange;
|
|
||||||
case 'completed':
|
|
||||||
return Colors.green;
|
|
||||||
case 'cancelled':
|
|
||||||
return Colors.red;
|
|
||||||
default:
|
|
||||||
return Colors.grey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getStatusLabel(String status) {
|
|
||||||
switch (status) {
|
|
||||||
case 'scheduled':
|
|
||||||
return 'Planned';
|
|
||||||
case 'in_progress':
|
|
||||||
return 'In Progress';
|
|
||||||
case 'completed':
|
|
||||||
return 'Completed';
|
|
||||||
case 'cancelled':
|
|
||||||
return 'Cancelled';
|
|
||||||
default:
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatTime(DateTime time) {
|
|
||||||
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
final statusColor = _getStatusColor(route.status);
|
|
||||||
final startTimeStr = _formatTime(route.startTime);
|
|
||||||
|
|
||||||
return Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
final scheduleService = ScheduleService();
|
|
||||||
await scheduleService.updateRouteStatus(route.id, 'in_progress');
|
|
||||||
} catch (_) {
|
|
||||||
// Ignore status update errors here; navigation should still open.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => NavigationScreen(route: route),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// Surface navigation errors to the user so they can report/log.
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Navigatiefout: ${e.toString()}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
splashFactory: InkSparkle.splashFactory,
|
|
||||||
child: Card(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
elevation: 0,
|
|
||||||
color: colorScheme.surface,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Header: Line number + Status
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
// Line number - Large and prominent
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [colorScheme.primary, colorScheme.tertiary],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Line ${route.busLine.lineNumber}',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Status badge
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: statusColor.withOpacity(0.14),
|
|
||||||
border: Border.all(color: statusColor),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
_getStatusLabel(route.status),
|
|
||||||
style: TextStyle(
|
|
||||||
color: statusColor,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Route info: Start -> End
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'From',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
route.busLine.startStop,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_forward,
|
|
||||||
color: colorScheme.outline,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'To',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
route.busLine.endStop,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Bus info and time
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
// Bus type
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Bus',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${route.busType.name} (${route.busType.seatCapacity} seats)',
|
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
route.busType.licensePlate,
|
|
||||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Start time
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Start Time',
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
startTimeStr,
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${route.busLine.estimatedDuration} min duration',
|
|
||||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
flutter/ephemeral
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# 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 "bus_terminal")
|
|
||||||
# The unique GTK application identifier for this application. See:
|
|
||||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
|
||||||
set(APPLICATION_ID "com.example.bus_terminal")
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# 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}
|
|
||||||
)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
|
||||||
|
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//
|
|
||||||
// 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_
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#
|
|
||||||
# Generated file, do not edit.
|
|
||||||
#
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
|
||||||
)
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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}")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
#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, "bus_terminal");
|
|
||||||
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, "bus_terminal");
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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_
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Flutter-related
|
|
||||||
**/Flutter/ephemeral/
|
|
||||||
**/Pods/
|
|
||||||
|
|
||||||
# Xcode-related
|
|
||||||
**/dgph
|
|
||||||
**/xcuserdata/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
import FlutterMacOS
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import geolocator_apple
|
|
||||||
import shared_preferences_foundation
|
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|
||||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
|
||||||
}
|
|
||||||
@@ -1,705 +0,0 @@
|
|||||||
// !$*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 /* bus_terminal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "bus_terminal.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 /* bus_terminal.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 /* bus_terminal.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.busTerminal.RunnerTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bus_terminal.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bus_terminal";
|
|
||||||
};
|
|
||||||
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.busTerminal.RunnerTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bus_terminal.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bus_terminal";
|
|
||||||
};
|
|
||||||
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.busTerminal.RunnerTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bus_terminal.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bus_terminal";
|
|
||||||
};
|
|
||||||
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 */;
|
|
||||||
}
|
|
||||||