Add initial project files and configurations for bus_running_record app

This commit is contained in:
ImBenji 2026-03-25 17:19:53 +00:00
parent f5da563c29
commit e41e14e252
151 changed files with 9829 additions and 0 deletions

50
.gitignore vendored Normal file
View file

@ -0,0 +1,50 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
/examples
/tools
/.claude
CLAUDE.md

45
.metadata Normal file
View file

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "6c794842101b5805e74774cce9f1fdb49cbcd13c"
channel: "beta"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
- platform: android
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
- platform: ios
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
- platform: linux
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
- platform: macos
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
- platform: web
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
- platform: windows
create_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
base_revision: 6c794842101b5805e74774cce9f1fdb49cbcd13c
# 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'

1275
MVP.md Normal file

File diff suppressed because it is too large Load diff

28
analysis_options.yaml Normal file
View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
android/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View file

@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "net.imbenji.bus_running_record"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "net.imbenji.bus_running_record"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -0,0 +1,50 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions for file picker -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:label="bus_running_record"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View file

@ -0,0 +1,6 @@
package net.imbenji.bus_running_record;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

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

@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View file

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

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View file

@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

BIN
assets/arriva_brr.xls Normal file

Binary file not shown.

BIN
assets/arriva_brr.xlsx Normal file

Binary file not shown.

34
ios/.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>

View file

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View file

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

43
ios/Podfile Normal file
View file

@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

89
ios/Podfile.lock Normal file
View file

@ -0,0 +1,89 @@
PODS:
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SDWebImage (5.21.1):
- SDWebImage/Core (= 5.21.1)
- SDWebImage/Core (5.21.1)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
SDWebImage: f29024626962457f3470184232766516dee8dfea
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
COCOAPODS: 1.16.2

View file

@ -0,0 +1,731 @@
// !$*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 */; };
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 */; };
C536F9E59C502B71681ADDC5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7850BC6B3D5EF5598AF87AE /* Pods_Runner.framework */; };
CA308A35B71C23EC5BE5FDF6 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 426174207BBAFA88D7CFC055 /* Pods_RunnerTests.framework */; };
/* 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 */
101675F695B183204FA25DBD /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
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>"; };
426174207BBAFA88D7CFC055 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4BD882B9AF5C6858919DB046 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
4CB19CB7D641D60E2EE70B43 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
63B09DAA5B1C037C51189B14 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
705184138D178718B0334B7F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
743E182B8FF6303634509D81 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; 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>"; };
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>"; };
A7850BC6B3D5EF5598AF87AE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C536F9E59C502B71681ADDC5 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D26A0418BEF3D5A31E9BFAA7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CA308A35B71C23EC5BE5FDF6 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
144173BB156148CFE3426E37 /* Pods */ = {
isa = PBXGroup;
children = (
63B09DAA5B1C037C51189B14 /* Pods-Runner.debug.xcconfig */,
4BD882B9AF5C6858919DB046 /* Pods-Runner.release.xcconfig */,
743E182B8FF6303634509D81 /* Pods-Runner.profile.xcconfig */,
4CB19CB7D641D60E2EE70B43 /* Pods-RunnerTests.debug.xcconfig */,
705184138D178718B0334B7F /* Pods-RunnerTests.release.xcconfig */,
101675F695B183204FA25DBD /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
3D1F8F2AA3410F79205689A8 /* Frameworks */ = {
isa = PBXGroup;
children = (
A7850BC6B3D5EF5598AF87AE /* Pods_Runner.framework */,
426174207BBAFA88D7CFC055 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
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 */,
144173BB156148CFE3426E37 /* Pods */,
3D1F8F2AA3410F79205689A8 /* Frameworks */,
);
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 */,
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 = (
C0D2A0384396A13F133E1D1F /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
D26A0418BEF3D5A31E9BFAA7 /* Frameworks */,
);
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 = (
AC368B5C461C0EEDC5F3A674 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
A0D3EE5AB60CC7EBE679E33A /* [CP] Embed Pods Frameworks */,
);
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";
};
A0D3EE5AB60CC7EBE679E33A /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
AC368B5C461C0EEDC5F3A674 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
C0D2A0384396A13F133E1D1F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* 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 */,
);
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)";
DEVELOPMENT_TEAM = A9TMA2CA43;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord;
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;
baseConfigurationReference = 4CB19CB7D641D60E2EE70B43 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord.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;
baseConfigurationReference = 705184138D178718B0334B7F /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord.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;
baseConfigurationReference = 101675F695B183204FA25DBD /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord.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)";
DEVELOPMENT_TEAM = A9TMA2CA43;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord;
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)";
DEVELOPMENT_TEAM = A9TMA2CA43;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View file

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

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

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

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,16 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}

View file

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View file

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

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

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Bus Running Record</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_running_record</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>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

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

View file

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View file

@ -0,0 +1,8 @@
class ScheduleParseException implements Exception {
final String message;
ScheduleParseException(this.message);
@override
String toString() => "ScheduleParseException: $message";
}

View file

@ -0,0 +1,127 @@
import "dart:convert";
import "dart:typed_data";
import "package:archive/archive.dart";
import "package:excel/excel.dart";
import "package:flutter/services.dart";
import "../models/trip.dart";
import "../models/brr_metadata.dart";
import "brr_exporter.dart";
class ArrivaBRRExporter implements BRRExporter {
// Numbers xlsx export leaves numFmtId="0" in custom formats which the
// excel package rejects. Strip it out before decoding.
List<int> _patchTemplateBytes(List<int> bytes) {
final archive = ZipDecoder().decodeBytes(bytes);
final output = Archive();
for (final file in archive) {
if (file.name == "xl/styles.xml" && file.isFile) {
var xml = utf8.decode(file.content as List<int>);
// strip the whole numFmts block Numbers export puts built-in IDs
// in there which the excel package rejects
xml = xml.replaceAll(RegExp(r'<numFmts[^>]*>.*?</numFmts>', dotAll: true), "");
// reset all numFmtId refs in xf elements to 0 (General)
// so nothing tries to look up the stripped formats
xml = xml.replaceAll(RegExp(r'numFmtId="\d+"'), 'numFmtId="0"');
final patched = utf8.encode(xml);
output.addFile(ArchiveFile(file.name, patched.length, patched));
} else {
output.addFile(file);
}
}
return ZipEncoder().encode(output)!;
}
static const int _dataStartRow = 8; // row 9 (0-indexed)
static const int _templateDataRows = 15; // rows 923
@override
Future<Uint8List> export(List<Trip> trips, BRRMetadata metadata) async {
final templateBytes = await rootBundle.load("assets/arriva_brr.xlsx");
final patched = _patchTemplateBytes(templateBytes.buffer.asUint8List());
final excel = Excel.decodeBytes(patched);
final sheetName = excel.sheets.keys.first;
final sheet = excel[sheetName];
if (trips.length > _templateDataRows) {
_shiftRowsDown(sheet, trips.length - _templateDataRows);
}
_populateData(sheet, trips);
final bytes = excel.encode();
if (bytes == null) throw Exception("Failed to encode Excel");
return Uint8List.fromList(bytes);
}
// Shifts all rows from (_dataStartRow + _templateDataRows) onwards down by extraRows
void _shiftRowsDown(Sheet sheet, int extraRows) {
final firstRowToShift = _dataStartRow + _templateDataRows; // row 24 (index 23)
// figure out how many rows exist beyond the data block
final maxRow = sheet.rows.length;
// copy from the bottom up to avoid overwriting
for (var r = maxRow - 1; r >= firstRowToShift; r--) {
final destRow = r + extraRows;
if (r >= sheet.rows.length) continue;
final srcRow = sheet.rows[r];
for (var c = 0; c < srcRow.length; c++) {
final cell = srcRow[c];
if (cell == null) continue;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: destRow)).value =
cell.value;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: destRow)).cellStyle =
cell.cellStyle;
}
}
// clear the original rows that were shifted
for (var r = firstRowToShift; r < firstRowToShift + extraRows; r++) {
if (r >= sheet.rows.length) break;
for (var c = 0; c < 18; c++) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: r)).value = null;
}
}
}
void _populateData(Sheet sheet, List<Trip> trips) {
for (var i = 0; i < trips.length; i++) {
final trip = trips[i];
final row = _dataStartRow + i;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: row)).value =
TextCellValue(trip.scheduledTime);
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: row)).value =
TextCellValue(trip.tripNumber);
if (trip.actualDepartureTime != null) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: row)).value =
TextCellValue(trip.actualDepartureTime!);
}
if (trip.actualFleetNumber != null) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: row)).value =
TextCellValue(trip.actualFleetNumber!);
}
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: row)).value =
TextCellValue(trip.dutyNumber);
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: row)).value =
TextCellValue(trip.runningNumber);
final didOperate = trip.actualDepartureTime != null && trip.actualFleetNumber != null;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 6, rowIndex: row)).value =
TextCellValue(didOperate ? "Y" : "N");
}
}
}

View file

@ -0,0 +1,7 @@
import "dart:typed_data";
import "../models/trip.dart";
import "../models/brr_metadata.dart";
abstract class BRRExporter {
Future<Uint8List> export(List<Trip> trips, BRRMetadata metadata);
}

View file

@ -0,0 +1,76 @@
import "dart:typed_data";
import "package:excel/excel.dart";
import "../models/trip.dart";
import "../models/brr_metadata.dart";
import "brr_exporter.dart";
class StagecoachBRRExporter implements BRRExporter {
@override
Future<Uint8List> export(List<Trip> trips, BRRMetadata metadata) async {
final excel = Excel.createExcel();
final sheetName = "BRR";
excel.rename(excel.getDefaultSheet()!, sheetName);
final sheet = excel[sheetName];
final headers = [
"Dep. Time",
"(+/-) No.",
"Ser.",
"Bus Wk No",
"Fleet No.",
"Crew Duty",
"No.of Pax",
"Notes",
];
final bold = CellStyle(bold: true);
for (var c = 0; c < headers.length; c++) {
final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: 0));
cell.value = TextCellValue(headers[c]);
cell.cellStyle = bold;
}
for (var i = 0; i < trips.length; i++) {
final trip = trips[i];
final row = i + 1;
// Dep Time (HHMM no colon)
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: row)).value =
TextCellValue(trip.scheduledTime.replaceAll(":", ""));
// (+/-) No. - user fills in
if (trip.actualDepartureTime != null) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: row)).value =
TextCellValue(trip.actualDepartureTime!);
}
// Ser. outbound shows route name, inbound shows "PARK"
// For now derive from station order: last outbound station as label
final ser = trip.direction == "outbound"
? (metadata.route != "Unknown" ? metadata.route : "OUT")
: "PARK";
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: row)).value =
TextCellValue(ser);
// Bus Wk No
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: row)).value =
TextCellValue(trip.dutyNumber);
// Fleet No. actual fleet number entered by user
if (trip.actualFleetNumber != null) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: row)).value =
TextCellValue(trip.actualFleetNumber!);
}
// Crew Duty
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: row)).value =
TextCellValue(trip.tripNumber);
}
final bytes = excel.encode();
if (bytes == null) throw Exception("Failed to encode excel");
return Uint8List.fromList(bytes);
}
}

101
lib/main.dart Normal file
View file

@ -0,0 +1,101 @@
import "package:flutter/material.dart";
import "package:go_router/go_router.dart";
import "package:hive_flutter/hive_flutter.dart";
import "pages/home_page.dart";
import "pages/station_selection_page.dart";
import "pages/trip_list_page.dart";
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final _router = GoRouter(
routes: [
HomePage.route,
StationSelectionPage.route,
TripListPage.route,
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
title: "Bus Running Record",
theme: ThemeData(
colorScheme: const ColorScheme.dark(
primary: Color(0xFF00A9CE),
onPrimary: Colors.black,
surface: Color(0xFF1E1E1E),
onSurface: Color(0xFFEEEEEE),
surfaceContainerHighest: Color(0xFF2A2A2A),
error: Color(0xFFCF6679),
),
scaffoldBackgroundColor: const Color(0xFF121212),
cardTheme: const CardThemeData(
color: Color(0xFF1E1E1E),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
side: BorderSide(color: Color(0xFF2E2E2E)),
),
),
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF1A1A1A),
foregroundColor: Color(0xFFEEEEEE),
elevation: 0,
titleTextStyle: TextStyle(
color: Color(0xFFEEEEEE),
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
borderSide: BorderSide(color: Color(0xFF3A3A3A)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
borderSide: BorderSide(color: Color(0xFF3A3A3A)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
borderSide: BorderSide(color: Color(0xFF00A9CE), width: 1.5),
),
filled: true,
fillColor: Color(0xFF252525),
labelStyle: TextStyle(color: Color(0xFF999999)),
hintStyle: TextStyle(color: Color(0xFF555555)),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00A9CE),
foregroundColor: Colors.black,
elevation: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
),
textStyle: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 14,
letterSpacing: 0.5,
),
),
),
dividerTheme: const DividerThemeData(
color: Color(0xFF2E2E2E),
thickness: 1,
),
useMaterial3: true,
),
);
}
}

View file

@ -0,0 +1,51 @@
class BRRMetadata {
final String route; // "Metropolitan_Line_Uxbridge"
final String date; // "21_09_2025"
final String location; // "Uxbridge Station"
final String controller; // "Benjamin Watt"
final String? sheetNumber;
BRRMetadata({
required this.route,
required this.date,
required this.location,
required this.controller,
this.sheetNumber,
});
factory BRRMetadata.fromSchedule(String scheduleText) {
// Extract metadata from schedule header
final routeMatch = RegExp(r"ROUTE\s+(\w+)").firstMatch(scheduleText);
final dateMatch =
RegExp(r"(\d{1,2})\s+(\d{1,2})\s+(\d{2})").firstMatch(scheduleText);
return BRRMetadata(
route: routeMatch?.group(1) ?? "Unknown",
date: dateMatch != null
? "${dateMatch.group(1)}_${dateMatch.group(2)}_20${dateMatch.group(3)}"
: DateTime.now().toString().split(" ")[0],
location: "Unknown",
controller: "Unknown",
);
}
Map<String, dynamic> toJson() {
return {
"route": route,
"date": date,
"location": location,
"controller": controller,
"sheetNumber": sheetNumber,
};
}
factory BRRMetadata.fromJson(Map<String, dynamic> json) {
return BRRMetadata(
route: json["route"] as String,
date: json["date"] as String,
location: json["location"] as String,
controller: json["controller"] as String,
sheetNumber: json["sheetNumber"] as String?,
);
}
}

58
lib/models/brr_state.dart Normal file
View file

@ -0,0 +1,58 @@
import "trip.dart";
class BRRState {
final List<Trip> trips;
final String? scheduleFileName;
final DateTime? uploadedAt;
final String? routeInfo; // e.g., "Metropolitan Line - Uxbridge"
final String? serviceDate; // "21/09/2025"
BRRState({
this.trips = const [],
this.scheduleFileName,
this.uploadedAt,
this.routeInfo,
this.serviceDate,
});
BRRState copyWith({
List<Trip>? trips,
String? scheduleFileName,
DateTime? uploadedAt,
String? routeInfo,
String? serviceDate,
}) {
return BRRState(
trips: trips ?? this.trips,
scheduleFileName: scheduleFileName ?? this.scheduleFileName,
uploadedAt: uploadedAt ?? this.uploadedAt,
routeInfo: routeInfo ?? this.routeInfo,
serviceDate: serviceDate ?? this.serviceDate,
);
}
Map<String, dynamic> toJson() {
return {
"trips": trips.map((trip) => trip.toJson()).toList(),
"scheduleFileName": scheduleFileName,
"uploadedAt": uploadedAt?.toIso8601String(),
"routeInfo": routeInfo,
"serviceDate": serviceDate,
};
}
factory BRRState.fromJson(Map<String, dynamic> json) {
return BRRState(
trips: (json["trips"] as List?)
?.map((tripJson) => Trip.fromJson(tripJson as Map<String, dynamic>))
.toList() ??
[],
scheduleFileName: json["scheduleFileName"] as String?,
uploadedAt: json["uploadedAt"] != null
? DateTime.parse(json["uploadedAt"] as String)
: null,
routeInfo: json["routeInfo"] as String?,
serviceDate: json["serviceDate"] as String?,
);
}
}

91
lib/models/trip.dart Normal file
View file

@ -0,0 +1,91 @@
class Trip {
final String scheduledTime; // "15:31" - default departure time (first non-empty time)
final String tripNumber; // "112"
final String dutyNumber; // "518"
final String runningNumber; // "518"
final String tripType; // "", "N", "R", "F"
final bool isFinishing;
final Map<String, String> stationTimes; // {"UXBG": "15:31", "HILL": "15:42", ...}
final List<String> stationOrder; // ["UXBG", "UXBG", "HILL", "ICKE", ...]
final String direction; // "outbound" or "inbound"
String? actualDepartureTime; // "15:33" (nullable - user input)
String? actualFleetNumber; // "33523" (nullable - user input)
Trip({
required this.scheduledTime,
required this.tripNumber,
required this.dutyNumber,
required this.runningNumber,
this.tripType = "",
this.isFinishing = false,
this.stationTimes = const {},
this.stationOrder = const [],
this.direction = "outbound",
this.actualDepartureTime,
this.actualFleetNumber,
});
bool get isComplete =>
actualDepartureTime != null && actualFleetNumber != null;
Trip copyWith({
String? scheduledTime,
String? tripNumber,
String? dutyNumber,
String? runningNumber,
String? tripType,
bool? isFinishing,
Map<String, String>? stationTimes,
List<String>? stationOrder,
String? direction,
String? actualDepartureTime,
String? actualFleetNumber,
}) {
return Trip(
scheduledTime: scheduledTime ?? this.scheduledTime,
tripNumber: tripNumber ?? this.tripNumber,
dutyNumber: dutyNumber ?? this.dutyNumber,
runningNumber: runningNumber ?? this.runningNumber,
tripType: tripType ?? this.tripType,
isFinishing: isFinishing ?? this.isFinishing,
stationTimes: stationTimes ?? this.stationTimes,
stationOrder: stationOrder ?? this.stationOrder,
direction: direction ?? this.direction,
actualDepartureTime: actualDepartureTime ?? this.actualDepartureTime,
actualFleetNumber: actualFleetNumber ?? this.actualFleetNumber,
);
}
Map<String, dynamic> toJson() {
return {
"scheduledTime": scheduledTime,
"tripNumber": tripNumber,
"dutyNumber": dutyNumber,
"runningNumber": runningNumber,
"tripType": tripType,
"isFinishing": isFinishing,
"stationTimes": stationTimes,
"stationOrder": stationOrder,
"direction": direction,
"actualDepartureTime": actualDepartureTime,
"actualFleetNumber": actualFleetNumber,
};
}
factory Trip.fromJson(Map<String, dynamic> json) {
return Trip(
scheduledTime: json["scheduledTime"] as String,
tripNumber: json["tripNumber"] as String,
dutyNumber: json["dutyNumber"] as String,
runningNumber: json["runningNumber"] as String,
tripType: json["tripType"] as String? ?? "",
isFinishing: json["isFinishing"] as bool? ?? false,
stationTimes: Map<String, String>.from(json["stationTimes"] as Map? ?? {}),
stationOrder: List<String>.from(json["stationOrder"] as List? ?? []),
direction: json["direction"] as String? ?? "outbound",
actualDepartureTime: json["actualDepartureTime"] as String?,
actualFleetNumber: json["actualFleetNumber"] as String?,
);
}
}

287
lib/pages/home_page.dart Normal file
View file

@ -0,0 +1,287 @@
import "package:flutter/material.dart";
import "package:file_picker/file_picker.dart";
import "package:go_router/go_router.dart";
import "../models/trip.dart";
import "../parsers/arriva_schedule_parser.dart";
import "../parsers/stagecoach_schedule_parser.dart";
import "../services/brr_export_service.dart";
import "../services/storage_service.dart";
class HomePage extends StatefulWidget {
const HomePage({super.key});
static GoRoute route = GoRoute(
path: "/",
builder: (context, state) => const HomePage(),
);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _isUploading = false;
String? _errorMessage;
BRROperator _selectedOperator = BRROperator.arriva;
@override
void initState() {
super.initState();
_restoreSession();
}
Future<void> _restoreSession() async {
final state = await StorageService().loadState();
if (state != null && state.trips.isNotEmpty && mounted) {
context.go("/trips", extra: {
"trips": state.trips,
"fileName": state.scheduleFileName ?? "",
"operator": _selectedOperator,
});
}
}
String get _fileExtension {
switch (_selectedOperator) {
case BRROperator.arriva:
return "docx";
case BRROperator.stagecoach:
return "pdf";
}
}
String get _formatLabel {
switch (_selectedOperator) {
case BRROperator.arriva:
return "Arriva schedule format (.docx)";
case BRROperator.stagecoach:
return "Stagecoach schedule format (.pdf)";
}
}
Future<void> _uploadSchedule() async {
print("Upload button pressed");
setState(() {
_isUploading = true;
_errorMessage = null;
});
try {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: [_fileExtension],
allowMultiple: false,
withData: true,
);
if (result == null || result.files.isEmpty) {
setState(() { _isUploading = false; });
return;
}
final file = result.files.first;
if (file.bytes == null) throw Exception("Could not read file bytes");
String? routeName;
final List<Trip> trips;
if (_selectedOperator == BRROperator.stagecoach) {
final parser = StagecoachScheduleParser();
trips = await parser.parseBytes(file.bytes!);
routeName = parser.parsedRouteName;
} else {
trips = await ArrivaScheduleParser().parseBytes(file.bytes!);
}
if (trips.isEmpty) throw Exception("No trips found in schedule");
if (mounted) {
context.go("/station-selection", extra: {
"trips": trips,
"fileName": file.name,
"operator": _selectedOperator,
if (routeName != null) "routeName": routeName,
});
}
} catch (e) {
print("Error: $e");
setState(() {
_errorMessage = e.toString();
});
} finally {
setState(() { _isUploading = false; });
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 40),
const Text(
"BUS\nRUNNING\nRECORD",
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w900,
height: 1.0,
letterSpacing: -1,
color: Color(0xFFEEEEEE),
),
),
const SizedBox(height: 8),
Container(
width: 40,
height: 3,
color: const Color(0xFF00A9CE),
),
const Spacer(),
// operator selector
const Text(
"OPERATOR",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFF777777),
letterSpacing: 1.5,
),
),
const SizedBox(height: 8),
Row(
children: [
_OperatorChip(
label: "ARRIVA",
selected: _selectedOperator == BRROperator.arriva,
onTap: () => setState(() => _selectedOperator = BRROperator.arriva),
),
const SizedBox(width: 8),
_OperatorChip(
label: "STAGECOACH",
selected: _selectedOperator == BRROperator.stagecoach,
onTap: () => setState(() => _selectedOperator = BRROperator.stagecoach),
),
],
),
const SizedBox(height: 16),
// format info
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFF2E2E2E)),
borderRadius: BorderRadius.circular(3),
),
child: Row(
children: [
const Icon(Icons.info_outline, size: 16, color: Color(0xFF777777)),
const SizedBox(width: 10),
Text(
_formatLabel,
style: const TextStyle(fontSize: 13, color: Color(0xFF777777)),
),
],
),
),
const SizedBox(height: 12),
if (_errorMessage != null) ...[
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF2A1515),
border: Border.all(color: const Color(0xFF7A2020)),
borderRadius: BorderRadius.circular(3),
),
child: Text(
_errorMessage!,
style: const TextStyle(
color: Color(0xFFCF6679),
fontSize: 13,
),
),
),
const SizedBox(height: 12),
],
SizedBox(
width: double.infinity,
height: 54,
child: ElevatedButton(
onPressed: _isUploading ? null : _uploadSchedule,
child: _isUploading
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.black,
),
)
: const Text("LOAD SCHEDULE"),
),
),
],
),
),
),
);
}
}
class _OperatorChip extends StatelessWidget {
final String label;
final bool selected;
final VoidCallback onTap;
const _OperatorChip({
required this.label,
required this.selected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: selected ? const Color(0xFF002A33) : const Color(0xFF1E1E1E),
border: Border.all(
color: selected ? const Color(0xFF00A9CE) : const Color(0xFF2E2E2E),
width: selected ? 1.5 : 1,
),
borderRadius: BorderRadius.circular(3),
),
child: Center(
child: Text(
label,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: selected ? const Color(0xFF00A9CE) : const Color(0xFF777777),
letterSpacing: 1,
),
),
),
),
),
);
}
}

View file

@ -0,0 +1,334 @@
import "package:flutter/material.dart";
import "package:go_router/go_router.dart";
import "../models/trip.dart";
import "../services/brr_export_service.dart";
class StationSelectionPage extends StatefulWidget {
final List<Trip> trips;
final String fileName;
final BRROperator operator;
final String? routeName;
const StationSelectionPage({
super.key,
required this.trips,
required this.fileName,
this.operator = BRROperator.arriva,
this.routeName,
});
static GoRoute route = GoRoute(
path: "/station-selection",
builder: (context, state) {
final extra = state.extra as Map<String, dynamic>?;
if (extra == null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.go("/");
});
return const SizedBox.shrink();
}
return StationSelectionPage(
trips: (extra["trips"] as List).cast<Trip>(),
fileName: extra["fileName"] as String,
operator: extra["operator"] as BRROperator? ?? BRROperator.arriva,
routeName: extra["routeName"] as String?,
);
},
);
@override
State<StationSelectionPage> createState() => _StationSelectionPageState();
}
class _StationSelectionPageState extends State<StationSelectionPage> {
String? _selectedStation;
String? _selectedDirection;
List<String> _availableStations = [];
@override
void initState() {
super.initState();
_extractStations();
}
void _extractStations() {
final stationsSet = <String>{};
for (final trip in widget.trips) {
stationsSet.addAll(trip.stationOrder);
}
_availableStations = stationsSet.toList()..sort();
}
bool _isTerminus(String station) {
for (final trip in widget.trips) {
if (trip.stationOrder.isEmpty) continue;
if (trip.stationOrder.first == station || trip.stationOrder.last == station) {
return true;
}
}
return false;
}
// For a given station, find where buses are headed in each direction
String? _getTerminus(String station, String direction) {
for (final trip in widget.trips) {
if (!trip.stationOrder.contains(station)) continue;
if (trip.stationOrder.isEmpty) continue;
// "departing" = outbound trips, show where they end up
if (direction == "departing" && trip.direction == "outbound") {
return trip.stationOrder.last;
}
// "arriving" = inbound trips, show where they end up (opposite terminus)
if (direction == "arriving" && trip.direction == "inbound") {
return trip.stationOrder.last;
}
}
return null;
}
String _getDirectionLabel(String direction) {
if (_selectedStation == null) {
return direction == "departing" ? "DEPARTING" : "ARRIVING";
}
if (_isTerminus(_selectedStation!)) {
return direction == "departing" ? "DEPARTURES" : "ARRIVALS";
}
final terminus = _getTerminus(_selectedStation!, direction);
return "towards\n${terminus ?? "..."}";
}
void _onContinue() {
if (_selectedStation == null || _selectedDirection == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Select both a station and direction")),
);
return;
}
final filteredTrips = _filterAndUpdateTrips();
if (filteredTrips.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("No trips found for that selection")),
);
return;
}
context.go("/trips", extra: {
"trips": filteredTrips,
"fileName": widget.fileName,
"station": _selectedStation,
"direction": _selectedDirection,
"operator": widget.operator,
if (widget.routeName != null) "routeName": widget.routeName,
});
}
List<Trip> _filterAndUpdateTrips() {
final filtered = <Trip>[];
final isTerminusStation = _isTerminus(_selectedStation!);
for (final trip in widget.trips) {
if (!trip.stationTimes.containsKey(_selectedStation)) continue;
if (_selectedDirection != "both") {
// For terminus stations, the direction logic flips:
// "departing" from a terminus that's first on inbound = show inbound trips
// "arriving" at a terminus thats last on outbound = show outbound trips
final isFirstStation = trip.stationOrder.isNotEmpty && trip.stationOrder.first == _selectedStation;
final isLastStation = trip.stationOrder.isNotEmpty && trip.stationOrder.last == _selectedStation;
if (isTerminusStation) {
if (_selectedDirection == "departing" && !isFirstStation) continue;
if (_selectedDirection == "arriving" && !isLastStation) continue;
} else {
if (_selectedDirection == "departing" && trip.direction != "outbound") continue;
if (_selectedDirection == "arriving" && trip.direction != "inbound") continue;
}
}
final stationTime = trip.stationTimes[_selectedStation];
if (stationTime != null) {
filtered.add(trip.copyWith(scheduledTime: stationTime));
}
}
return filtered;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, size: 20),
onPressed: () => context.go("/"),
),
title: const Text("SELECT STATION"),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// file info strip
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
color: const Color(0xFF1E1E1E),
child: Row(
children: [
const Icon(Icons.description_outlined, size: 14, color: Color(0xFF777777)),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.fileName,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF777777),
),
overflow: TextOverflow.ellipsis,
),
),
Text(
"${widget.trips.length} trips",
style: const TextStyle(
fontSize: 12,
color: Color(0xFF00A9CE),
fontWeight: FontWeight.w600,
),
),
],
),
),
const SizedBox(height: 28),
const Text(
"STATION",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFF777777),
letterSpacing: 1.5,
),
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
initialValue: _selectedStation,
dropdownColor: const Color(0xFF252525),
style: const TextStyle(color: Color(0xFFEEEEEE), fontSize: 15),
decoration: const InputDecoration(
hintText: "Choose station...",
),
items: _availableStations.map((station) {
return DropdownMenuItem(
value: station,
child: Text(station),
);
}).toList(),
onChanged: (value) => setState(() => _selectedStation = value),
),
const SizedBox(height: 24),
const Text(
"DIRECTION",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFF777777),
letterSpacing: 1.5,
),
),
const SizedBox(height: 8),
Row(
children: [
_DirButton(
label: _getDirectionLabel("departing"),
selected: _selectedDirection == "departing",
onTap: () => setState(() => _selectedDirection = "departing"),
),
const SizedBox(width: 10),
_DirButton(
label: _getDirectionLabel("arriving"),
selected: _selectedDirection == "arriving",
onTap: () => setState(() => _selectedDirection = "arriving"),
),
if (widget.operator == BRROperator.stagecoach) ...[
const SizedBox(width: 10),
_DirButton(
label: "BOTH",
selected: _selectedDirection == "both",
onTap: () => setState(() => _selectedDirection = "both"),
),
],
],
),
const Spacer(),
SizedBox(
width: double.infinity,
height: 54,
child: ElevatedButton(
onPressed: _onContinue,
child: const Text("VIEW TRIPS"),
),
),
],
),
),
),
);
}
}
class _DirButton extends StatelessWidget {
final String label;
final bool selected;
final VoidCallback onTap;
const _DirButton({
required this.label,
required this.selected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
decoration: BoxDecoration(
color: selected ? const Color(0xFF002A33) : const Color(0xFF1E1E1E),
border: Border.all(
color: selected ? const Color(0xFF00A9CE) : const Color(0xFF2E2E2E),
width: selected ? 1.5 : 1,
),
borderRadius: BorderRadius.circular(3),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: selected ? const Color(0xFF00A9CE) : const Color(0xFF777777),
letterSpacing: 0.3,
),
),
),
),
);
}
}

View file

@ -0,0 +1,871 @@
import "dart:io";
import "dart:typed_data";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:file_saver/file_saver.dart";
import "package:go_router/go_router.dart";
import "package:path_provider/path_provider.dart";
import "package:share_plus/share_plus.dart";
import "../models/trip.dart";
import "../models/brr_metadata.dart";
import "../models/brr_state.dart";
import "../services/brr_export_service.dart";
import "../services/storage_service.dart";
class TripListPage extends StatefulWidget {
final List<Trip> trips;
final String fileName;
final bool fromStationSelection;
final BRROperator operator;
final String? routeName;
const TripListPage({
super.key,
required this.trips,
required this.fileName,
this.fromStationSelection = false,
this.operator = BRROperator.arriva,
this.routeName,
});
static GoRoute route = GoRoute(
path: "/trips",
builder: (context, state) {
final extra = state.extra as Map<String, dynamic>?;
if (extra == null) {
// extra is lost on hot reload home will restore from storage
WidgetsBinding.instance.addPostFrameCallback((_) {
context.go("/");
});
return const SizedBox.shrink();
}
return TripListPage(
trips: (extra["trips"] as List).cast<Trip>(),
fileName: extra["fileName"] as String,
fromStationSelection: extra.containsKey("station"),
operator: extra["operator"] as BRROperator? ?? BRROperator.arriva,
routeName: extra["routeName"] as String?,
);
},
);
@override
State<TripListPage> createState() => _TripListPageState();
}
class _TripListPageState extends State<TripListPage> {
late List<Trip> _trips;
bool _isExporting = false;
String? _expandedTripId;
final _storageService = StorageService();
@override
void initState() {
super.initState();
_trips = List.from(widget.trips);
if (widget.operator == BRROperator.arriva) {
_trips.sort((a, b) => int.tryParse(a.tripNumber)?.compareTo(int.tryParse(b.tripNumber) ?? 0) ?? a.tripNumber.compareTo(b.tripNumber));
} else {
final firstMins = _toMins(_trips.first.scheduledTime);
_trips.sort((a, b) => _sortableTime(a.scheduledTime, firstMins).compareTo(_sortableTime(b.scheduledTime, firstMins)));
}
_autoSave();
}
int _toMins(String time) {
final parts = time.split(":");
if (parts.length != 2) return 0;
return (int.tryParse(parts[0]) ?? 0) * 60 + (int.tryParse(parts[1]) ?? 0);
}
// Anything earlier than the first trip's time is treated as next-day.
int _sortableTime(String time, int thresholdMins) {
final mins = _toMins(time);
return mins < thresholdMins ? mins + 1440 : mins;
}
Future<void> _autoSave() async {
final state = BRRState(
trips: _trips,
scheduleFileName: widget.fileName,
uploadedAt: DateTime.now(),
);
await _storageService.saveState(state);
}
int get _completedCount => _trips.where((t) => t.isComplete).length;
String get _exportFileName =>
"BRR_${DateTime.now().toString().split(" ")[0].replaceAll("-", "_")}.xlsx";
Future<void> _handleExport() async {
setState(() => _isExporting = true);
try {
final metadata = BRRMetadata(
route: widget.routeName ?? "Unknown",
date: DateTime.now().toString().split(" ")[0].replaceAll("-", "_"),
location: "Unknown",
controller: "Unknown",
);
final exportService = BRRExportService(operator: widget.operator);
final result = await exportService.exportBRR(_trips, metadata);
if (!mounted) return;
if (result.isSuccess) {
await _saveFile(result.bytes!, _exportFileName);
} else {
if (mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Cannot Export"),
content: Text(result.errors.join("\n")),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("OK"),
),
],
),
);
}
}
} finally {
if (mounted) setState(() => _isExporting = false);
}
}
Future<void> _saveFile(Uint8List bytes, String fileName) async {
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
final tmp = await getTemporaryDirectory();
final file = File("${tmp.path}/$fileName");
await file.writeAsBytes(bytes);
await Share.shareXFiles(
[XFile(file.path)],
subject: "BRR ${DateTime.now().toString().split(" ")[0]}",
);
} else {
await FileSaver.instance.saveFile(
name: fileName.replaceAll(".xlsx", ""),
bytes: bytes,
ext: "xlsx",
mimeType: MimeType.microsoftExcel,
);
}
}
void _updateTrip(int index, Trip updatedTrip) {
setState(() {
_trips[index] = updatedTrip;
});
_autoSave();
}
@override
Widget build(BuildContext context) {
final progress = _trips.isEmpty ? 0.0 : _completedCount / _trips.length;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, size: 20),
onPressed: () async {
await _storageService.clearState();
if (!mounted) return;
if (widget.fromStationSelection) {
context.go("/station-selection", extra: {
"trips": widget.trips,
"fileName": widget.fileName,
});
} else {
context.go("/");
}
},
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("TRIPS"),
Text(
widget.fileName,
style: const TextStyle(
fontSize: 11,
color: Color(0xFF777777),
fontWeight: FontWeight.w400,
),
),
],
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 12),
child: TextButton(
onPressed: _isExporting ? null : _handleExport,
style: TextButton.styleFrom(
backgroundColor: const Color(0xFF00A9CE),
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
),
textStyle: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 12,
letterSpacing: 0.5,
),
),
child: _isExporting
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black),
)
: const Text("EXPORT"),
),
),
],
),
body: Column(
children: [
// progress bar
Container(
color: const Color(0xFF1A1A1A),
padding: const EdgeInsets.fromLTRB(16, 10, 16, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"$_completedCount / ${_trips.length}",
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xFFEEEEEE),
fontFamily: "monospace",
),
),
Text(
"${(progress * 100).toStringAsFixed(0)}% complete",
style: const TextStyle(
fontSize: 12,
color: Color(0xFF777777),
),
),
],
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
value: progress,
minHeight: 4,
backgroundColor: const Color(0xFF2E2E2E),
valueColor: const AlwaysStoppedAnimation(Color(0xFF00A9CE)),
),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _trips.length,
padding: const EdgeInsets.symmetric(vertical: 8),
itemBuilder: (context, index) {
final trip = _trips[index];
final tripId = "${trip.tripNumber}_$index";
final isExpanded = _expandedTripId == tripId;
// find the next trip this duty runs after this one
final nextDutyTrip = _trips
.skip(index + 1)
.where((t) => t.dutyNumber == trip.dutyNumber)
.firstOrNull;
return TripCard(
trip: trip,
nextDutyTrip: nextDutyTrip,
isExpanded: isExpanded,
onTap: () {
setState(() {
_expandedTripId = isExpanded ? null : tripId;
});
},
onUpdate: (updatedTrip) => _updateTrip(index, updatedTrip),
);
},
),
),
],
),
);
}
}
class TripCard extends StatefulWidget {
final Trip trip;
final Trip? nextDutyTrip;
final bool isExpanded;
final VoidCallback onTap;
final Function(Trip) onUpdate;
const TripCard({
super.key,
required this.trip,
this.nextDutyTrip,
required this.isExpanded,
required this.onTap,
required this.onUpdate,
});
@override
State<TripCard> createState() => _TripCardState();
}
class _TripCardState extends State<TripCard> {
late TextEditingController _actualTimeController;
late TextEditingController _actualFleetController;
@override
void initState() {
super.initState();
_actualTimeController = TextEditingController(text: widget.trip.actualDepartureTime ?? "");
_actualFleetController = TextEditingController(text: widget.trip.actualFleetNumber ?? "");
}
@override
void dispose() {
_actualTimeController.dispose();
_actualFleetController.dispose();
super.dispose();
}
void _saveChanges() {
final updated = widget.trip.copyWith(
actualDepartureTime: _actualTimeController.text.isEmpty ? null : _actualTimeController.text,
actualFleetNumber: _actualFleetController.text.isEmpty ? null : _actualFleetController.text,
);
widget.onUpdate(updated);
}
void _setSameAsScheduled() {
_actualTimeController.text = widget.trip.scheduledTime;
_saveChanges();
}
void _setNow() {
final now = DateTime.now();
_actualTimeController.text =
"${now.hour.toString().padLeft(2, "0")}:${now.minute.toString().padLeft(2, "0")}";
_saveChanges();
}
@override
Widget build(BuildContext context) {
final isComplete = widget.trip.isComplete;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF1E1E1E),
border: Border(
left: BorderSide(
color: isComplete ? const Color(0xFF4CAF50) : const Color(0xFF2E2E2E),
width: 3,
),
),
),
child: InkWell(
onTap: widget.onTap,
child: Column(
children: [
// collapsed row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
child: Row(
children: [
// time
SizedBox(
width: 72,
child: Text(
widget.trip.scheduledTime,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900,
fontFamily: "monospace",
color: Color(0xFFEEEEEE),
letterSpacing: -0.5,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF252525),
border: Border.all(color: const Color(0xFF2E2E2E)),
borderRadius: BorderRadius.circular(3),
),
child: Text(
"Trip ${widget.trip.tripNumber}",
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFFCCCCCC),
),
),
),
const SizedBox(height: 2),
],
),
),
if (widget.trip.isFinishing)
Container(
margin: const EdgeInsets.only(right: 8),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
color: const Color(0xFF2A1A00),
child: const Text(
"FIN",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: Color(0xFF00A9CE),
letterSpacing: 1,
),
),
),
Icon(
widget.isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
size: 18,
color: const Color(0xFF555555),
),
],
),
),
// expanded section
if (widget.isExpanded) ...[
Container(height: 1, color: const Color(0xFF2E2E2E)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// scheduled info grid
Row(
children: [
_InfoCell("DUTY", widget.trip.dutyNumber),
const SizedBox(width: 12),
_InfoCell("RUNNING", widget.trip.runningNumber),
],
),
const SizedBox(height: 16),
// actual departure time
const Text(
"ACTUAL TIME",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: Color(0xFF666666),
letterSpacing: 1.5,
),
),
const SizedBox(height: 6),
Row(
children: [
Expanded(
child: SizedBox(
height: 48,
child: TextField(
controller: _actualTimeController,
style: const TextStyle(
fontFamily: "monospace",
fontSize: 16,
fontWeight: FontWeight.w700,
),
decoration: InputDecoration(
hintText: "HH:MM",
suffixIcon: _actualTimeController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.close, size: 16),
onPressed: () {
_actualTimeController.clear();
_saveChanges();
},
)
: null,
),
onChanged: (_) { setState(() {}); _saveChanges(); },
),
),
),
const SizedBox(width: 8),
SizedBox(
height: 48,
child: OutlinedButton(
onPressed: _setNow,
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF999999),
side: const BorderSide(color: Color(0xFF3A3A3A)),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
textStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600),
),
child: const Text("NOW"),
),
),
const SizedBox(width: 8),
SizedBox(
height: 48,
child: OutlinedButton(
onPressed: _setSameAsScheduled,
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF999999),
side: const BorderSide(color: Color(0xFF3A3A3A)),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(3)),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
textStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600),
),
child: const Text("AS SCHED"),
),
),
],
),
const SizedBox(height: 14),
// actual fleet
const Text(
"ACTUAL FLEET",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: Color(0xFF666666),
letterSpacing: 1.5,
),
),
const SizedBox(height: 6),
Row(
children: [
Expanded(
child: TextField(
controller: _actualFleetController,
style: const TextStyle(
fontFamily: "monospace",
fontSize: 16,
fontWeight: FontWeight.w700,
),
decoration: const InputDecoration(
hintText: "EC###",
),
onChanged: (_) => _saveChanges(),
),
),
],
),
],
),
),
Container(height: 1, color: const Color(0xFF2E2E2E)),
Padding(
padding: const EdgeInsets.all(16.0),
child: const Text(
"SCHEDULE",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: Color(0xFF666666),
letterSpacing: 1.5,
),
),
),
_ScheduleDiagram(
stations: widget.trip.stationOrder,
stationTimes: widget.trip.stationTimes,
nextDutyTrip: widget.nextDutyTrip,
leftOffset: 16,
),
Container(height: 1, color: const Color(0xFF2E2E2E)),
SizedBox(height: 16),
],
),
],
],
),
),
);
}
}
class _ScheduleDiagram extends StatelessWidget {
final List<String> stations;
final Map<String, String> stationTimes;
final Trip? nextDutyTrip;
final double leftOffset;
const _ScheduleDiagram({required this.stations, required this.stationTimes, this.nextDutyTrip, this.leftOffset = 0});
static const double rowHeight = 32;
static const double lineW = 7;
static const double dotSize = 15;
@override
Widget build(BuildContext context) {
final lineColor = Theme.of(context).colorScheme.primary;
final placeholderLabel = nextDutyTrip != null
? "BECOMES: TRIP ${nextDutyTrip!.tripNumber}"
: "END OF DUTY";
final placeholderTime = nextDutyTrip?.scheduledTime;
// deduplicate stops with the same name + time anywhere in the list
final deduped = <String>[];
final seen = <String>{};
for (final s in stations) {
final key = "$s|${stationTimes[s]}";
if (seen.contains(key)) continue;
seen.add(key);
deduped.add(s);
}
final displayStations = [...deduped, placeholderLabel];
final displayTimes = Map<String, String>.from(stationTimes);
if (placeholderTime != null) displayTimes[placeholderLabel] = placeholderTime;
final totalHeight = displayStations.length * rowHeight;
return Stack(
children: [
// the painted line + dots behind the labels
SizedBox(
height: totalHeight,
width: double.infinity,
child: CustomPaint(
painter: _LineDiagramPainter(
count: displayStations.length,
rowHeight: rowHeight,
lineW: lineW,
dotSize: dotSize,
lineColor: lineColor,
leftOffset: leftOffset,
),
),
),
// labels column on top
Positioned.fill(child: Column(
children: List.generate(displayStations.length, (i) {
final station = displayStations[i];
final time = displayTimes[station];
return Container(
height: rowHeight,
color: i == displayStations.length - 1
? lineColor.withValues(alpha: 0.12)
: i.isOdd ? Colors.white.withValues(alpha: 0.03) : null,
child: Padding(
padding: EdgeInsets.only(left: leftOffset + 26, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
station,
style: const TextStyle(
fontSize: 13,
color: Color(0xFFDDDDDD),
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
Text(
time ?? "--:--",
style: TextStyle(
fontSize: 13,
fontFamily: "monospace",
fontWeight: FontWeight.w700,
color: time != null
? const Color(0xFFEEEEEE)
: const Color(0xFF555555),
),
),
],
),
),
);
}),
)),
],
);
}
}
class _LineDiagramPainter extends CustomPainter {
final int count;
final double rowHeight;
final double lineW;
final double dotSize;
final Color lineColor;
// metro_map_maker constants, scaled ~3.5x (world units screen px, base line = 6px world)
static const double _linePad = 3.0;
static const double _blobPad = 4.0;
static const double _centerSize = 4.0;
static const double _cornerFactor = 0.3;
final double leftOffset;
_LineDiagramPainter({
required this.count,
required this.rowHeight,
required this.lineW,
required this.dotSize,
required this.lineColor,
this.leftOffset = 0,
});
@override
void paint(Canvas canvas, Size size) {
// derived mirrors metro_map_maker getStationRingGeometry
final strokeW = lineW / 2.0; // baseStrokeWidth
final ringSize = _centerSize + strokeW; // kStationCenterSize + strokeW
final paddingWidth = strokeW + _blobPad; // kStationBlobPaddingWidth + strokeW
// cx: enough left margin for the station blob outline, plus caller offset
final cx = leftOffset + ringSize / 2 + paddingWidth / 2 + 2;
final firstY = rowHeight / 2;
final lastY = (count - 1) * rowHeight + rowHeight / 2;
// build rrects once for reuse
final rrects = List.generate(count, (i) {
final cy = i * rowHeight + rowHeight / 2;
final rect = Rect.fromCenter(center: Offset(cx, cy), width: ringSize, height: ringSize);
return RRect.fromRectAndRadius(rect, Radius.circular(ringSize * _cornerFactor));
});
// pass 1: station white padding (under everything)
for (final rrect in rrects) {
canvas.drawRRect(
rrect,
Paint()
..color = Colors.white
..strokeWidth = paddingWidth
..style = PaintingStyle.stroke,
);
}
// pass 2: line white padding
canvas.drawLine(
Offset(cx, firstY),
Offset(cx, lastY),
Paint()
..color = Colors.white
..strokeWidth = lineW + _linePad
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke,
);
// pass 3: colored line
canvas.drawLine(
Offset(cx, firstY),
Offset(cx, lastY),
Paint()
..color = lineColor
..strokeWidth = lineW
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke,
);
// pass 4: station white fill + colored ring (on top of line)
for (final rrect in rrects) {
final centerRRect = rrect.deflate(strokeW / 2);
canvas.drawRRect(centerRRect, Paint()..color = Colors.white..style = PaintingStyle.fill);
canvas.drawRRect(
rrect,
Paint()
..color = lineColor
..strokeWidth = strokeW
..style = PaintingStyle.stroke,
);
}
}
@override
bool shouldRepaint(_LineDiagramPainter old) =>
old.count != count || old.lineColor != lineColor || old.lineW != lineW || old.leftOffset != leftOffset;
}
class _InfoCell extends StatelessWidget {
final String label;
final String value;
const _InfoCell(this.label, this.value);
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
color: const Color(0xFF252525),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 9,
fontWeight: FontWeight.w700,
color: Color(0xFF666666),
letterSpacing: 1.2,
),
),
const SizedBox(height: 3),
Text(
value,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFFCCCCCC),
fontFamily: "monospace",
),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
}

View file

@ -0,0 +1,409 @@
import "dart:typed_data";
import "package:docx_to_text/docx_to_text.dart";
import "../models/trip.dart";
import "../exceptions/schedule_parse_exception.dart";
import "schedule_parser.dart";
class ArrivaScheduleParser implements ScheduleParser {
// OUTBOUND FORMAT: HHMM_HHMM times... EC### duty running trip
// Times appear on the LEFT, trip info on the RIGHT
static final _outboundPattern = RegExp(
r"^(\d{4})_(\d{4})\s+(.*?)\s+(EC\d+)\s+(\d+)\s+([NRF]?)(\d+)\s+(\d+)",
);
static final _outboundFinishingPattern = RegExp(
r"^(\d{4})_(\d{4})\s+(.*?)\s+(EC\d+)\s+(\d+)\s+F",
);
// INBOUND FORMAT: trip duty running EC### ... HHMM_HHMM times...
// Trip info appears on the LEFT, times on the RIGHT
// Note: running number may have N/R/F prefix like "N503" or be separate like "N 503"
static final _inboundPattern = RegExp(
r"^(\d+)\s+(\d+)\s+([NRF]?)(\d+)\s+(EC\d+)\s+.*?(\d{4})_(\d{4})\s+([\d\s]+)",
);
static final _inboundFinishingPattern = RegExp(
r"^(\d+)\s+(\d+)\s+F\s+(EC\d+)\s+.*?(\d{4})_(\d{4})(.*)",
);
@override
bool canParse(String content) {
// Check if content looks like Arriva format
return content.contains("EC5") &&
content.contains("FINISH AT POINT") &&
RegExp(r"\d{4}_\d{4}").hasMatch(content);
}
@override
Future<List<Trip>> parseBytes(Uint8List bytes) async {
// Step 1: Extract text
final text = _extractTextFromDocx(bytes);
// DEBUG: Print extracted text
print("=== EXTRACTED TEXT START ===");
print(text);
print("=== EXTRACTED TEXT END ===");
// Step 2: Parse document structure (headers and trips)
final lines = text.split("\n");
final documentSections = _parseDocumentSections(lines);
print("=== FOUND ${documentSections.length} SECTIONS ===");
for (var section in documentSections) {
print("Section: ${section.direction}, ${section.stations.length} stations, ${section.tripLines.length} trips");
}
if (documentSections.isEmpty) {
throw ScheduleParseException("No trip data found in schedule");
}
// Step 3: Parse trips from all sections
final trips = <Trip>[];
for (var section in documentSections) {
final sectionTrips = _parseSectionTrips(section);
trips.addAll(sectionTrips);
print("✓ Parsed ${sectionTrips.length} trips from ${section.direction} section");
}
// Step 4: Sort by scheduled time
trips.sort((a, b) => a.scheduledTime.compareTo(b.scheduledTime));
return trips;
}
String _extractTextFromDocx(Uint8List bytes) {
try {
return docxToText(bytes);
} catch (e) {
throw ScheduleParseException("Failed to read document: $e");
}
}
String _formatTime(String rawTime) {
if (rawTime.length != 4) {
throw FormatException("Invalid time format: $rawTime");
}
return "${rawTime.substring(0, 2)}:${rawTime.substring(2, 4)}";
}
List<_DocumentSection> _parseDocumentSections(List<String> lines) {
final sections = <_DocumentSection>[];
_DocumentSection? currentSection;
for (var i = 0; i < lines.length; i++) {
final line = lines[i].trim();
// Check if this is a station header line
final stations = _extractStationHeader(line);
if (stations != null && stations.isNotEmpty) {
// Save previous section if it exists
if (currentSection != null && currentSection.tripLines.isNotEmpty) {
sections.add(currentSection);
}
// Direction is determined later from the first trip line
currentSection = _DocumentSection(
stations: stations,
direction: "unknown",
tripLines: [],
);
print("Found station header at line $i with ${stations.length} stations");
print(" Stations: ${stations.take(3).join(", ")} ... ${stations.skip(stations.length - 2).join(", ")}");
continue;
}
// Check if this is a trip line
if (currentSection != null && _isTripLine(line)) {
// Infer direction from first trip line if not yet set
if (currentSection.direction == "unknown") {
currentSection.direction = _inferDirectionFromTripLine(line);
print(" Direction inferred: ${currentSection.direction}");
}
currentSection.tripLines.add(line);
}
}
// Add final section
if (currentSection != null && currentSection.tripLines.isNotEmpty) {
sections.add(currentSection);
}
return sections;
}
List<String>? _extractStationHeader(String line) {
// Station headers have multiple short uppercase codes, no digits, no underscores
if (line.contains(RegExp(r"\d")) ||
line.contains("EC") ||
line.contains("_") ||
line.length < 10) {
return null;
}
// Split by whitespace and filter for potential station codes (3-8 uppercase letters)
final parts = line.split(RegExp(r"\s+"));
final potentialStations = parts
.where((part) => part.length >= 3 &&
part.length <= 8 &&
RegExp(r"^[A-Z]+$").hasMatch(part))
.toList();
if (potentialStations.length < 8) return null;
// Filter out common metadata words that appear in headers
const nonStationWords = {
"TRP", "DUTY", "BUS", "START", "END", "GAR", "DEP", "ARR",
"DENOTES", "FINISHES", "RELIEF", "TRIP", "NEXT", "NO", "AT",
"SPELL", "HOURS", "TOTAL", "LAYOVER", "MILES", "LIVE", "DEAD",
"MILEAGE", "TIME", "SIGN", "FORM", "NXT", "THIS", "HAS", "OR",
"FOR", "CHANGE", "SERVICE", "POINT", "LSN", "MAN", "RUI", "SN",
"ROUTE", "RUNNING", "PREV", "FIN", "ENTOD", "SOALL", "USHRS",
"ADDTL", "CASH", "TODAYS", "REL", "IEF",
};
final stations = potentialStations
.where((s) => !nonStationWords.contains(s))
.toList();
// Need at least 5 actual station-like codes remaining - the structural
// density of codes is what marks this as a station header, not a known list
return stations.length >= 5 ? stations : null;
}
// Determine direction from the format of the first trip line.
// Lines starting with digits are outbound (trip number comes first).
// Lines starting with underscores or a bare time are inbound (times come first).
String _inferDirectionFromTripLine(String line) {
if (RegExp(r"^\d{4}_\d{4}").hasMatch(line)) return "inbound";
if (RegExp(r"^_+\d{4}").hasMatch(line)) return "inbound";
if (RegExp(r"^\d+\s+\d+").hasMatch(line)) return "outbound";
return "outbound";
}
bool _isTripLine(String line) {
return RegExp(r"\d{4}_\d{4}").hasMatch(line) && line.contains("EC");
}
List<Trip> _parseSectionTrips(_DocumentSection section) {
final trips = <Trip>[];
for (final line in section.tripLines) {
Trip? trip;
// Detect actual line format by looking at structure
// Inbound: starts with numbers (trip duty running EC###) or (trip duty F EC###)
// Note: running number might be "N503 EC" (with spaces) or "N 503 EC" or just "503 EC"
// Outbound: starts with HHMM_HHMM
final isOutboundFormat = RegExp(r"^\d{4}_\d{4}").hasMatch(line);
final isInboundFormat = RegExp(r"^\d+\s+\d+\s+(?:[NRF]\d+\s+|[NRF]\s+\d+\s+|F\s+|\d+\s+)EC").hasMatch(line);
if (isOutboundFormat) {
trip = _parseOutboundTrip(line, section.stations);
} else if (isInboundFormat) {
trip = _parseInboundTrip(line, section.stations);
}
if (trip != null) {
trips.add(trip);
} else {
final format = isOutboundFormat ? "outbound" : isInboundFormat ? "inbound" : "unknown";
print("Failed to parse $format line: ${line.substring(0, line.length > 80 ? 80 : line.length)}...");
}
}
return trips;
}
Trip? _parseInboundTrip(
String line,
List<String> stations,
) {
// INBOUND: trip duty running EC### ... HHMM_HHMM times...
var match = _inboundPattern.firstMatch(line);
if (match != null) {
final tripNumber = match.group(1)!;
final dutyNumber = match.group(2)!;
final tripType = match.group(3) ?? "";
final runningNumber = match.group(4)!;
final firstTime = match.group(6)!;
final secondTime = match.group(7)!;
final timesString = match.group(8) ?? "";
// Build complete time array: first_time, second_time, then remaining times
final times = [firstTime, secondTime];
final additionalTimes = _extractTimesFromString(timesString);
times.addAll(additionalTimes);
final stationTimes = _mapStationsToTimes(stations, times);
final scheduledTime = _formatTime(firstTime);
return Trip(
tripNumber: tripNumber,
dutyNumber: dutyNumber,
runningNumber: runningNumber,
scheduledTime: scheduledTime,
tripType: tripType,
isFinishing: false,
stationTimes: stationTimes,
stationOrder: stations,
direction: (int.tryParse(tripNumber) ?? 0).isOdd ? "inbound" : "outbound",
);
}
// Try finishing pattern
match = _inboundFinishingPattern.firstMatch(line);
if (match != null) {
final tripNumber = match.group(1)!;
final dutyNumber = match.group(2)!;
final firstTime = match.group(4)!;
final secondTime = match.group(5)!;
final timesString = match.group(6) ?? "";
final times = [firstTime, secondTime];
final additionalTimes = _extractTimesFromString(timesString);
times.addAll(additionalTimes);
final stationTimes = _mapStationsToTimes(stations, times);
final scheduledTime = _formatTime(firstTime);
return Trip(
tripNumber: tripNumber,
dutyNumber: dutyNumber,
runningNumber: dutyNumber,
scheduledTime: scheduledTime,
tripType: "F",
isFinishing: true,
stationTimes: stationTimes,
stationOrder: stations,
direction: (int.tryParse(tripNumber) ?? 0).isOdd ? "inbound" : "outbound",
);
}
return null;
}
Trip? _parseOutboundTrip(
String line,
List<String> stations,
) {
// OUTBOUND: HHMM_HHMM times... EC### duty running trip
var match = _outboundPattern.firstMatch(line);
if (match != null) {
final firstTime = match.group(1)!;
final secondTime = match.group(2)!;
final timesString = match.group(3) ?? "";
final dutyNumber = match.group(5)!;
final tripType = match.group(6) ?? "";
final runningNumber = match.group(7)!;
final tripNumber = match.group(8)!;
// Build complete time array: first_time, second_time, then remaining times
final times = [firstTime, secondTime];
times.addAll(_extractTimesFromString(timesString));
final stationTimes = _mapStationsToTimes(stations, times);
final scheduledTime = _formatTime(firstTime);
return Trip(
tripNumber: tripNumber,
dutyNumber: dutyNumber,
runningNumber: runningNumber,
scheduledTime: scheduledTime,
tripType: tripType,
isFinishing: false,
stationTimes: stationTimes,
stationOrder: stations,
direction: (int.tryParse(tripNumber) ?? 0).isOdd ? "inbound" : "outbound",
);
}
// Try finishing pattern
match = _outboundFinishingPattern.firstMatch(line);
if (match != null) {
final firstTime = match.group(1)!;
final secondTime = match.group(2)!;
final timesString = match.group(3) ?? "";
final dutyNumber = match.group(5)!;
final times = [firstTime, secondTime];
times.addAll(_extractTimesFromString(timesString));
final stationTimes = _mapStationsToTimes(stations, times);
final scheduledTime = _formatTime(firstTime);
return Trip(
tripNumber: dutyNumber, // Finishing trips may not have separate trip number
dutyNumber: dutyNumber,
runningNumber: dutyNumber,
scheduledTime: scheduledTime,
tripType: "F",
isFinishing: true,
stationTimes: stationTimes,
stationOrder: stations,
direction: (int.tryParse(dutyNumber) ?? 0).isOdd ? "inbound" : "outbound",
);
}
return null;
}
List<String> _extractTimesFromString(String timesString) {
// Extract all 4-digit times from the string
final pattern = RegExp(r"\b(\d{4})\b");
return pattern
.allMatches(timesString)
.map((m) => m.group(1)!)
.toList();
}
List<String> _extractAllTimes(String line) {
// Extract all 4-digit times, including those in HHMM_HHMM format
final timePattern = RegExp(r"\b(\d{4})(?:_(\d{4}))?\b");
final matches = timePattern.allMatches(line);
final times = <String>[];
for (final match in matches) {
// Add first time
times.add(match.group(1)!);
// Add second time if it exists (from HHMM_HHMM)
if (match.group(2) != null) {
times.add(match.group(2)!);
}
}
return times;
}
Map<String, String> _mapStationsToTimes(
List<String> stations,
List<String> times,
) {
final stationTimes = <String, String>{};
for (var i = 0; i < stations.length && i < times.length; i++) {
final time = times[i];
// Only add non-empty times (not "____" or similar)
if (RegExp(r"^\d{4}$").hasMatch(time)) {
stationTimes[stations[i]] = _formatTime(time);
}
}
return stationTimes;
}
}
class _DocumentSection {
final List<String> stations;
String direction;
final List<String> tripLines;
_DocumentSection({
required this.stations,
required this.direction,
required this.tripLines,
});
}

View file

@ -0,0 +1,7 @@
import "dart:typed_data";
import "../models/trip.dart";
abstract class ScheduleParser {
Future<List<Trip>> parseBytes(Uint8List bytes);
bool canParse(String content);
}

View file

@ -0,0 +1,301 @@
import "dart:typed_data";
import "package:syncfusion_flutter_pdf/pdf.dart";
import "../models/trip.dart";
import "../exceptions/schedule_parse_exception.dart";
import "schedule_parser.dart";
class StagecoachScheduleParser implements ScheduleParser {
String? parsedRouteName;
@override
bool canParse(String content) {
final collapsed = content.replaceAll("\n", " ");
return collapsed.contains("ROUTE") &&
RegExp(r"\dD\d{3}").hasMatch(collapsed);
}
@override
Future<List<Trip>> parseBytes(Uint8List bytes) async {
final rawPages = _extractPages(bytes);
// Syncfusion splits text with newlines everywhere.
// Collapse each page into single line, normalize all whitespace runs to single space
final pages = rawPages.map((p) =>
p.replaceAll("\n", " ").replaceAll(RegExp(r"\s+"), " ").trim()
).toList();
print("=== STAGECOACH: ${pages.length} pages ===");
// Extract route name "ROUTE 099CSATURDAY..." or "ROUTE CRL SATURDAY..."
final routeMatch = RegExp(r"ROUTE\s+(\w+?)(?:SATURDAY|SUNDAY|MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY)", caseSensitive: false).firstMatch(pages.first);
if (routeMatch != null) {
parsedRouteName = routeMatch.group(1);
} else {
final fallback = RegExp(r"ROUTE\s+(\w+)").firstMatch(pages.first);
parsedRouteName = fallback?.group(1);
}
print("Route: $parsedRouteName");
final allTrips = <Trip>[];
final seenKeys = <String>{};
for (var pi = 0; pi < pages.length; pi++) {
final page = pages[pi];
// Direction: outbound pages have "DEPT GAR" in header
final isOutbound = RegExp(r"DEPT\s+GAR").hasMatch(page);
final direction = isOutbound ? "outbound" : "inbound";
final stations = _extractStations(page, isOutbound);
print("Page ${pi + 1}: $direction, stations: $stations");
final trips = _parseTripsFromPage(page, direction, stations);
print(" Trips found: ${trips.length}");
for (final t in trips) {
final key = "${t.tripNumber}_${t.scheduledTime}_${t.direction}";
if (seenKeys.contains(key)) continue;
seenKeys.add(key);
allTrips.add(t);
print(" TRIP: ${t.tripNumber} | ${t.scheduledTime} | ${t.direction} | duty=${t.dutyNumber} run=${t.runningNumber} | stations=${t.stationTimes}");
}
}
if (allTrips.isEmpty) {
throw ScheduleParseException("No trips found in stagecoach schedule");
}
// keep original parse order for stagecoach
print("Total unique trips: ${allTrips.length}");
return allTrips;
}
List<String> _extractPages(Uint8List bytes) {
try {
final doc = PdfDocument(inputBytes: bytes);
final pages = <String>[];
for (int i = 0; i < doc.pages.count; i++) {
pages.add(PdfTextExtractor(doc).extractText(
startPageIndex: i, endPageIndex: i,
));
}
doc.dispose();
return pages;
} catch (e) {
throw ScheduleParseException("Failed to read PDF: $e");
}
}
List<String> _extractStations(String pageText, bool isOutbound) {
// After full normalization (all whitespace = single space), the header looks like:
// "...RLF STS: DEPT GAR STFC BS T STFC BS FGAT PA ... ROMF SN T RLF DEPT TIME..."
// or inbound: "...RLF STS: ROMF SN T ROMF SN ... STFC BS T ARR GAR RLF DEPT TIME..."
//
// Station names are 4-letter codes followed by qualifiers: "STFC BS T", "MNPK BY" etc.
// In the syncfusion text each station name part was on its own line, so after
// collapsing they alternate: CODE QUALIFIER CODE QUALIFIER...
String? stationBlock;
if (isOutbound) {
// Between "DEPT GAR" and "RLF DEPT TIME" (or just before the first duty ID)
final match = RegExp(r"DEPT GAR (.+?) RLF DEPT TIME").firstMatch(pageText);
if (match != null) stationBlock = match.group(1);
} else {
// Between "RLF STS:" and "ARR GAR"
final match = RegExp(r"RLF STS: (.+?) ARR GAR").firstMatch(pageText);
if (match != null) stationBlock = match.group(1);
}
if (stationBlock == null) {
print(" No station header match");
return _extractStationsFallback(pageText);
}
return _pairStationTokens(stationBlock.trim());
}
List<String> _extractStationsFallback(String pageText) {
// broader: between STS: and first duty ID
final match = RegExp(r"STS: (.+?) \d[A-Z]\d{3}").firstMatch(pageText);
if (match == null) return [];
var block = match.group(1)!;
// Remove known non-station words
for (final word in ["DEPT GAR", "ARR GAR", "RLF DEPT TIME", "DUTY END",
"NEXT BUS", "NEXT TRIP", "DRV SPELL", "RLF"]) {
block = block.replaceAll(word, " ");
}
return _pairStationTokens(block.trim());
}
List<String> _pairStationTokens(String block) {
// Station names in stagecoach PDFs are structured as:
// 4-letter CODE followed by a qualifier (1-3 chars, possibly with T suffix)
// After normalization: "STFC BS T STFC BS FGAT PA MNPK BY ILFD BS"
//
// The pattern is: each station starts with a 3-4 uppercase letter code,
// followed by qualifier tokens until the next 3-4 letter code.
//
// Strategy: scan tokens, when we see a 3-4 letter all-caps token that looks
// like a station code, start a new station name. Append subsequent tokens
// as qualifiers until the next station code.
final tokens = block.split(" ").where((t) => t.isNotEmpty).toList();
final stations = <String>[];
var current = <String>[];
for (final token in tokens) {
// A new station code: 3-4 uppercase letters
if (RegExp(r"^[A-Z]{3,4}$").hasMatch(token) && current.isNotEmpty) {
stations.add(current.join(" "));
current = [token];
} else if (current.isEmpty && RegExp(r"^[A-Z]{3,4}$").hasMatch(token)) {
current = [token];
} else {
current.add(token);
}
}
if (current.isNotEmpty) {
stations.add(current.join(" "));
}
return stations;
}
List<Trip> _parseTripsFromPage(String pageText, String direction, List<String> stations) {
final trips = <Trip>[];
// Find all trip-start positions: DUTY_ID followed by RUN(digits) and BUS(3 digits)
// This pattern marks the beginning of a real trip entry
final tripStartPattern = RegExp(r"\b((\d/)?\d[A-Z]\d{3})\s+(\d+)\s+(\d{3})\b");
final starts = tripStartPattern.allMatches(pageText).toList();
print(" Trip-start matches: ${starts.length}");
int parsed = 0;
int failed = 0;
for (var i = 0; i < starts.length; i++) {
final segStart = starts[i].start;
// segment ends where the next trip starts, or at end of text
final segEnd = (i + 1 < starts.length) ? starts[i + 1].start : pageText.length;
final segment = pageText.substring(segStart, segEnd).trim();
final dutyId = starts[i].group(1)!;
final runNumber = starts[i].group(3)!;
final busWorkingNo = starts[i].group(4)!;
final trip = _parseTripFromSegment(segment, dutyId, runNumber, busWorkingNo, direction, stations);
if (trip != null) {
trips.add(trip);
parsed++;
} else {
failed++;
final preview = segment.length > 120 ? segment.substring(0, 120) : segment;
print(" Skip: $preview");
}
}
print(" Parsed: $parsed, skipped: $failed");
return trips;
}
Trip? _parseTripFromSegment(
String segment, String dutyId, String runNumber, String busWorkingNo,
String direction, List<String> stations,
) {
// Skip header junk
if (segment.contains("TRIP NO") ||
segment.contains("DUTY START") || segment.contains("SCHEDULE DATE")) {
return null;
}
// Skip deadhead/light runs but only if LIGHT appears early in the segment
// (before station times), not in trailing text from the next concatenated entry
final lightMatch = RegExp(r"LIGHT", caseSensitive: false).firstMatch(segment);
if (lightMatch != null) {
final headerEnd = RegExp(RegExp.escape(dutyId) + r"\s+\d+\s+\d{3}").firstMatch(segment)?.end ?? 0;
// if LIGHT is within 40 chars of the header, its a genuine deadhead
if (lightMatch.start - headerEnd < 40) {
return null;
}
}
// Strip the leading "DUTY RUN BUS" we already extracted
final afterHeader = segment.substring(
RegExp(RegExp.escape(dutyId) + r"\s+\d+\s+\d{3}").firstMatch(segment)!.end
).trim();
// Strip relief/garage prefix to get to the times
var timesSection = afterHeader;
// remove leading relief markers and garage name
timesSection = timesSection.replaceFirst(RegExp(r"^(RR|FR|FRX|FX|R)\s*"), "");
timesSection = timesSection.replaceFirst(RegExp(r"^HomeGara\s*"), "");
// Also remove leading text from inbound like just "R " or "F "
timesSection = timesSection.replaceFirst(RegExp(r"^(F|R)\s+"), "");
// Extract all 4-digit time values
final times = RegExp(r"\b(\d{4})\b")
.allMatches(timesSection)
.map((m) => m.group(1)!)
.where((t) {
final h = int.tryParse(t.substring(0, 2)) ?? 99;
return h < 30;
})
.toList();
if (times.isEmpty) return null;
// For garage trips, first time is the garage departure skip it
final isFromGarage = afterHeader.contains("HomeGara");
int firstStationIdx = 0;
if (isFromGarage && times.length > 1) {
firstStationIdx = 1;
}
// Only use the first N times (where N = station count) to avoid
// grabbing times from the trailing metadata (spell, next bus etc)
final stationTimes = <String, String>{};
for (var i = 0; i < stations.length; i++) {
final timeIdx = firstStationIdx + i;
if (timeIdx < times.length) {
stationTimes[stations[i]] = _formatTime(times[timeIdx]);
}
}
if (stationTimes.isEmpty) return null;
final scheduledTime = _formatTime(times[firstStationIdx]);
String tripType = "";
if (afterHeader.startsWith("RR") || afterHeader.startsWith("R")) tripType = "R";
final actualDirection = dutyId.startsWith("2/") ? "inbound" : direction;
// If trip direction doesnt match page direction,
// reverse station order so terminus is correct
final tripStations = actualDirection != direction
? stations.reversed.toList()
: List<String>.from(stations);
return Trip(
scheduledTime: scheduledTime,
tripNumber: dutyId,
dutyNumber: busWorkingNo,
runningNumber: runNumber,
tripType: tripType,
isFinishing: segment.contains("BUS FIN"),
stationTimes: stationTimes,
stationOrder: tripStations,
direction: actualDirection,
);
}
String _formatTime(String rawTime) {
if (rawTime.length != 4) return rawTime;
return "${rawTime.substring(0, 2)}:${rawTime.substring(2, 4)}";
}
}

View file

@ -0,0 +1,44 @@
import "dart:typed_data";
import "../models/trip.dart";
import "../models/brr_metadata.dart";
import "../exporters/brr_exporter.dart";
import "../exporters/arriva_brr_exporter.dart";
import "../exporters/stagecoach_brr_exporter.dart";
enum BRROperator { arriva, stagecoach }
class ExportResult {
final Uint8List? bytes;
final List<String> errors;
bool get isSuccess => errors.isEmpty;
ExportResult.success(this.bytes) : errors = [];
ExportResult.error(this.errors) : bytes = null;
}
class BRRExportService {
final BRROperator operator;
BRRExportService({this.operator = BRROperator.arriva});
BRRExporter get _exporter {
switch (operator) {
case BRROperator.arriva:
return ArrivaBRRExporter();
case BRROperator.stagecoach:
return StagecoachBRRExporter();
}
}
Future<ExportResult> exportBRR(
List<Trip> trips,
BRRMetadata metadata,
) async {
try {
final bytes = await _exporter.export(trips, metadata);
return ExportResult.success(bytes);
} catch (e) {
return ExportResult.error(["Export failed: $e"]);
}
}
}

View file

@ -0,0 +1,33 @@
import "dart:convert";
import "package:hive_flutter/hive_flutter.dart";
import "../models/brr_state.dart";
class StorageService {
static const _boxName = "brr_box";
static const _stateKey = "brr_state";
Future<Box<String>> _openBox() => Hive.openBox<String>(_boxName);
Future<void> saveState(BRRState state) async {
final box = await _openBox();
await box.put(_stateKey, jsonEncode(state.toJson()));
}
Future<BRRState?> loadState() async {
final box = await _openBox();
final jsonString = box.get(_stateKey);
if (jsonString == null) return null;
try {
return BRRState.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
} catch (e) {
return null;
}
}
Future<void> clearState() async {
final box = await _openBox();
await box.delete(_stateKey);
}
}

View file

@ -0,0 +1,34 @@
import "../models/trip.dart";
class TripValidator {
static List<String> validate(Trip trip) {
final errors = <String>[];
// Validate time format
if (!RegExp(r"^\d{2}:\d{2}$").hasMatch(trip.scheduledTime)) {
errors.add("Invalid time format: ${trip.scheduledTime}");
}
// Validate trip number
if (trip.tripNumber.isEmpty) {
errors.add("Missing trip number");
}
// Validate duty/running numbers
if (trip.dutyNumber.isEmpty || trip.runningNumber.isEmpty) {
errors.add("Missing duty or running number");
}
// Validate actual departure time if provided
if (trip.actualDepartureTime != null &&
!RegExp(r"^\d{2}:\d{2}$").hasMatch(trip.actualDepartureTime!)) {
errors.add("Invalid actual departure time format: ${trip.actualDepartureTime}");
}
return errors;
}
static bool isValid(Trip trip) {
return validate(trip).isEmpty;
}
}

1
linux/.gitignore vendored Normal file
View file

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

128
linux/CMakeLists.txt Normal file
View file

@ -0,0 +1,128 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "bus_running_record")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "net.imbenji.bus_running_record")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View file

@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View file

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View file

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -0,0 +1,25 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
file_saver
url_launcher_linux
)
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)

View file

@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")

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

@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

View file

@ -0,0 +1,144 @@
#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_running_record");
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_running_record");
}
gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000 for transparent.
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
// Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

View file

@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

7
macos/.gitignore vendored Normal file
View file

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

View file

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View file

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View file

@ -0,0 +1,18 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import file_picker
import file_saver
import path_provider_foundation
import share_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
}

42
macos/Podfile Normal file
View file

@ -0,0 +1,42 @@
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

22
macos/Podfile.lock Normal file
View file

@ -0,0 +1,22 @@
PODS:
- file_saver (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
DEPENDENCIES:
- file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
EXTERNAL SOURCES:
file_saver:
:path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos
FlutterMacOS:
:path: Flutter/ephemeral
SPEC CHECKSUMS:
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
COCOAPODS: 1.16.2

View file

@ -0,0 +1,823 @@
// !$*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 */
077D5573FB91E6A68013933B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA84D6ACDC69A559838B707 /* Pods_Runner.framework */; };
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 */; };
CE2E75326CA342D72D4E3C12 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D24EE89447FFAAE0992CC3F /* Pods_RunnerTests.framework */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* 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 */
040A1F381C9A8E1DAB3A5C7C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
2C24E4B05DF4DFC2DA8BDF0A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
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_running_record.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bus_running_record.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>"; };
4D24EE89447FFAAE0992CC3F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
507DC5851A611EEF24EED5CB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
5C278E47667B2280D1E8E01C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.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>"; };
A6B5367A8A38D53ECBF54EDF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
A7E2CE1CA37C0AD388E71727 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
EAA84D6ACDC69A559838B707 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CE2E75326CA342D72D4E3C12 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
077D5573FB91E6A68013933B /* Pods_Runner.framework in Frameworks */,
);
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 */,
D352E0A93876CDB05BFC98F1 /* Pods */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* bus_running_record.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 = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
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>";
};
D352E0A93876CDB05BFC98F1 /* Pods */ = {
isa = PBXGroup;
children = (
A7E2CE1CA37C0AD388E71727 /* Pods-Runner.debug.xcconfig */,
507DC5851A611EEF24EED5CB /* Pods-Runner.release.xcconfig */,
5C278E47667B2280D1E8E01C /* Pods-Runner.profile.xcconfig */,
A6B5367A8A38D53ECBF54EDF /* Pods-RunnerTests.debug.xcconfig */,
2C24E4B05DF4DFC2DA8BDF0A /* Pods-RunnerTests.release.xcconfig */,
040A1F381C9A8E1DAB3A5C7C /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
EAA84D6ACDC69A559838B707 /* Pods_Runner.framework */,
4D24EE89447FFAAE0992CC3F /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
4F9D0120407422A4EB5BA39F /* [CP] Check Pods Manifest.lock */,
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 */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
2227E04AB15DDA3FADF41941 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
97C32F068B1FEF1D778E00CF /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* bus_running_record.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
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 */
2227E04AB15DDA3FADF41941 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
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";
};
4F9D0120407422A4EB5BA39F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
97C32F068B1FEF1D778E00CF /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* 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;
baseConfigurationReference = A6B5367A8A38D53ECBF54EDF /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bus_running_record.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bus_running_record";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2C24E4B05DF4DFC2DA8BDF0A /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bus_running_record.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bus_running_record";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 040A1F381C9A8E1DAB3A5C7C /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.imbenji.busRunningRecord.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bus_running_record.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bus_running_record";
};
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 */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

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