From 51672e691ec96d789d8b53ac3ebe95a0830cc074 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 5 Jun 2026 19:03:29 +0100 Subject: [PATCH] Add hard parity screenshot smoke --- .gitignore | 1 + docs/parity-testing-harness.md | 90 +++ docs/renderer-parity.md | 13 +- docs/runtime-app-parity-roadmap.md | 5 + flutter/eve_parity/.gitignore | 45 ++ flutter/eve_parity/.metadata | 39 ++ flutter/eve_parity/README.md | 21 + flutter/eve_parity/analysis_options.yaml | 28 + flutter/eve_parity/android/.gitignore | 14 + .../eve_parity/android/app/build.gradle.kts | 45 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 ++ .../com/example/eve_parity/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + flutter/eve_parity/android/build.gradle.kts | 24 + flutter/eve_parity/android/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../eve_parity/android/settings.gradle.kts | 26 + flutter/eve_parity/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 24 + flutter/eve_parity/ios/Flutter/Debug.xcconfig | 1 + .../eve_parity/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 644 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 119 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../eve_parity/ios/Runner/AppDelegate.swift | 16 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + flutter/eve_parity/ios/Runner/Info.plist | 70 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../eve_parity/ios/Runner/SceneDelegate.swift | 6 + .../ios/RunnerTests/RunnerTests.swift | 12 + flutter/eve_parity/lib/main.dart | 174 +++++ flutter/eve_parity/linux/.gitignore | 1 + flutter/eve_parity/linux/CMakeLists.txt | 128 ++++ .../eve_parity/linux/flutter/CMakeLists.txt | 88 +++ .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 23 + .../eve_parity/linux/runner/CMakeLists.txt | 26 + flutter/eve_parity/linux/runner/main.cc | 6 + .../eve_parity/linux/runner/my_application.cc | 148 ++++ .../eve_parity/linux/runner/my_application.h | 21 + flutter/eve_parity/pubspec.lock | 205 ++++++ flutter/eve_parity/pubspec.yaml | 83 +++ .../test/goldens/cultui-inspector.png | Bin 0 -> 82785 bytes .../eve_parity/test/parity_smoke_test.dart | 16 + flutter/eve_parity/windows/.gitignore | 17 + flutter/eve_parity/windows/CMakeLists.txt | 108 +++ .../eve_parity/windows/flutter/CMakeLists.txt | 109 +++ .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 23 + .../eve_parity/windows/runner/CMakeLists.txt | 40 ++ flutter/eve_parity/windows/runner/Runner.rc | 121 ++++ .../windows/runner/flutter_window.cpp | 71 ++ .../windows/runner/flutter_window.h | 33 + flutter/eve_parity/windows/runner/main.cpp | 43 ++ flutter/eve_parity/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 14 + flutter/eve_parity/windows/runner/utils.cpp | 69 ++ flutter/eve_parity/windows/runner/utils.h | 19 + .../windows/runner/win32_window.cpp | 288 ++++++++ .../eve_parity/windows/runner/win32_window.h | 102 +++ scripts/capture-android-screenshot.ps1 | 47 ++ scripts/capture-eve-screenshot.ps1 | 11 +- scripts/capture-flutter-parity.ps1 | 39 ++ scripts/capture-web-reference.ps1 | 50 ++ scripts/run-parity-harness.ps1 | 19 + scripts/run-parity-smoke.ps1 | 116 ++++ tools/parity/parity-manifest.json | 133 ++++ tools/parity/run-parity.mjs | 226 ++++++ web/surface.js | 9 +- 109 files changed, 4358 insertions(+), 9 deletions(-) create mode 100644 docs/parity-testing-harness.md create mode 100644 flutter/eve_parity/.gitignore create mode 100644 flutter/eve_parity/.metadata create mode 100644 flutter/eve_parity/README.md create mode 100644 flutter/eve_parity/analysis_options.yaml create mode 100644 flutter/eve_parity/android/.gitignore create mode 100644 flutter/eve_parity/android/app/build.gradle.kts create mode 100644 flutter/eve_parity/android/app/src/debug/AndroidManifest.xml create mode 100644 flutter/eve_parity/android/app/src/main/AndroidManifest.xml create mode 100644 flutter/eve_parity/android/app/src/main/kotlin/com/example/eve_parity/MainActivity.kt create mode 100644 flutter/eve_parity/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 flutter/eve_parity/android/app/src/main/res/drawable/launch_background.xml create mode 100644 flutter/eve_parity/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 flutter/eve_parity/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 flutter/eve_parity/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 flutter/eve_parity/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 flutter/eve_parity/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 flutter/eve_parity/android/app/src/main/res/values-night/styles.xml create mode 100644 flutter/eve_parity/android/app/src/main/res/values/styles.xml create mode 100644 flutter/eve_parity/android/app/src/profile/AndroidManifest.xml create mode 100644 flutter/eve_parity/android/build.gradle.kts create mode 100644 flutter/eve_parity/android/gradle.properties create mode 100644 flutter/eve_parity/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 flutter/eve_parity/android/settings.gradle.kts create mode 100644 flutter/eve_parity/ios/.gitignore create mode 100644 flutter/eve_parity/ios/Flutter/AppFrameworkInfo.plist create mode 100644 flutter/eve_parity/ios/Flutter/Debug.xcconfig create mode 100644 flutter/eve_parity/ios/Flutter/Release.xcconfig create mode 100644 flutter/eve_parity/ios/Runner.xcodeproj/project.pbxproj create mode 100644 flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 flutter/eve_parity/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 flutter/eve_parity/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 flutter/eve_parity/ios/Runner/AppDelegate.swift create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 flutter/eve_parity/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 flutter/eve_parity/ios/Runner/Base.lproj/Main.storyboard create mode 100644 flutter/eve_parity/ios/Runner/Info.plist create mode 100644 flutter/eve_parity/ios/Runner/Runner-Bridging-Header.h create mode 100644 flutter/eve_parity/ios/Runner/SceneDelegate.swift create mode 100644 flutter/eve_parity/ios/RunnerTests/RunnerTests.swift create mode 100644 flutter/eve_parity/lib/main.dart create mode 100644 flutter/eve_parity/linux/.gitignore create mode 100644 flutter/eve_parity/linux/CMakeLists.txt create mode 100644 flutter/eve_parity/linux/flutter/CMakeLists.txt create mode 100644 flutter/eve_parity/linux/flutter/generated_plugin_registrant.cc create mode 100644 flutter/eve_parity/linux/flutter/generated_plugin_registrant.h create mode 100644 flutter/eve_parity/linux/flutter/generated_plugins.cmake create mode 100644 flutter/eve_parity/linux/runner/CMakeLists.txt create mode 100644 flutter/eve_parity/linux/runner/main.cc create mode 100644 flutter/eve_parity/linux/runner/my_application.cc create mode 100644 flutter/eve_parity/linux/runner/my_application.h create mode 100644 flutter/eve_parity/pubspec.lock create mode 100644 flutter/eve_parity/pubspec.yaml create mode 100644 flutter/eve_parity/test/goldens/cultui-inspector.png create mode 100644 flutter/eve_parity/test/parity_smoke_test.dart create mode 100644 flutter/eve_parity/windows/.gitignore create mode 100644 flutter/eve_parity/windows/CMakeLists.txt create mode 100644 flutter/eve_parity/windows/flutter/CMakeLists.txt create mode 100644 flutter/eve_parity/windows/flutter/generated_plugin_registrant.cc create mode 100644 flutter/eve_parity/windows/flutter/generated_plugin_registrant.h create mode 100644 flutter/eve_parity/windows/flutter/generated_plugins.cmake create mode 100644 flutter/eve_parity/windows/runner/CMakeLists.txt create mode 100644 flutter/eve_parity/windows/runner/Runner.rc create mode 100644 flutter/eve_parity/windows/runner/flutter_window.cpp create mode 100644 flutter/eve_parity/windows/runner/flutter_window.h create mode 100644 flutter/eve_parity/windows/runner/main.cpp create mode 100644 flutter/eve_parity/windows/runner/resource.h create mode 100644 flutter/eve_parity/windows/runner/resources/app_icon.ico create mode 100644 flutter/eve_parity/windows/runner/runner.exe.manifest create mode 100644 flutter/eve_parity/windows/runner/utils.cpp create mode 100644 flutter/eve_parity/windows/runner/utils.h create mode 100644 flutter/eve_parity/windows/runner/win32_window.cpp create mode 100644 flutter/eve_parity/windows/runner/win32_window.h create mode 100644 scripts/capture-android-screenshot.ps1 create mode 100644 scripts/capture-flutter-parity.ps1 create mode 100644 scripts/capture-web-reference.ps1 create mode 100644 scripts/run-parity-harness.ps1 create mode 100644 scripts/run-parity-smoke.ps1 create mode 100644 tools/parity/parity-manifest.json create mode 100644 tools/parity/run-parity.mjs diff --git a/.gitignore b/.gitignore index c334802..31f93e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ packages/ obj/ artifacts/ +tools/deps/ *.deb *.ipa *.dSYM/ diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md new file mode 100644 index 0000000..33cd89a --- /dev/null +++ b/docs/parity-testing-harness.md @@ -0,0 +1,90 @@ +# Eve Parity Testing Harness + +The parity harness is the shared evidence path for Eve runtimes. It does not +try to prove byte-identical pixels. It proves that every runtime is aiming at +the same provider-owned CultUI surface: same fixture, same retained tree, same +style-token expectations, same control anatomy, and explicit capture gaps. + +## Authority + +- `tools/parity/parity-manifest.json` owns the fixture matrix, runtime list, and + expectations. +- `tools/parity/run-parity.mjs` owns semantic fixture checks and report + generation. +- Runtime capture adapters own screenshots or frame capture for their platform. +- `artifacts/parity/latest.md` and `artifacts/parity/latest.json` are reports, + not source truth. + +The manifest is the cut line. If a fixture or runtime is not in the manifest, +it is not part of the parity claim. + +## Run + +Semantic checks: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\run-parity-harness.ps1 +``` + +Hard screenshot smoke: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\run-parity-smoke.ps1 +``` + +The semantic runner writes: + +- `artifacts/parity/latest.md` +- `artifacts/parity/latest.json` +- `artifacts/parity//parity-report.md` +- `artifacts/parity//parity-report.json` + +The smoke runner writes: + +- `artifacts/parity-smoke//parity-smoke.md` +- `artifacts/parity-smoke//parity-smoke.json` +- one PNG per target that has a real capture body. + +## Current Checks + +The first pass is semantic and fixture-driven: + +- compile local CultUI `.eve` fixtures; +- load local JSON surface fixtures; +- check provider id; +- check required component kinds; +- check minimum component counts where useful; +- check required style tokens; +- check required bindings; +- check retained slider skins and control parts. + +This already catches the most embarrassing class of parity lie: a runtime or +fixture claiming to render CultUI while the retained tree no longer contains +the partitions, bindings, tokens, or slider anatomy the renderer is supposed to +lower. + +## Runtime Status + +The harness tracks every target runtime: + +- Web reference: active semantic and screenshot target through Chrome headless. +- iOS / UIKit: screenshot target through SSH and EveCanvas' + `/var/mobile/Library/EveCanvas/capture-request` service. +- Android / Kotlin: screenshot target through `adb install`, activity launch, + and `adb exec-out screencap`. +- Windows / Flutter: screenshot target through the Flutter parity golden smoke. +- Linux / Flutter: requires a Linux runner. A Windows host must fail this + target instead of pretending to be Linux. +- Fensalir Direct2D: specialized native target, adapter/capture still missing. + +Pending runtimes are allowed. Silent fake parity is not. + +## Next Cuts + +1. Move the Flutter parity renderer from hardcoded inspector anatomy to the + compiled `gamecult.eve.surface.v1` fixture. +2. Wire iOS and Android capture to select a manifest fixture before capture, + instead of only launching the current app body. +3. Add a Linux runner or WSL/Linux desktop capture body for Flutter. +4. Add Direct2D frame capture once the Eve-to-`AquariumUiDocument` adapter + exists. diff --git a/docs/renderer-parity.md b/docs/renderer-parity.md index fdc0d7b..a0d4d6b 100644 --- a/docs/renderer-parity.md +++ b/docs/renderer-parity.md @@ -14,14 +14,19 @@ recognizable CultUI fidelity: the same partitions, fonts, colors, panel treatments, spacing, image sampling intent, and control anatomy lower cleanly enough that Repixelizer remains itself across runtimes. +The shared fixture/report path lives in +[parity-testing-harness.md](./parity-testing-harness.md). That harness is the +current evidence surface for semantic parity and runtime capture gaps. + ## Current State | Renderer | Status | Proof | Gap | | --- | --- | --- | --- | -| Web reference | Active reference/debug renderer | `web/` renders live VoidBot and fixture surfaces through the provider picker; compiles indentation CultUI; lowers partitions, field rows, and slider anatomy | Needs visual regression fixtures and command round-trip tests | -| Flutter | Preferred shared graphical client candidate | Not installed locally | Needs toolchain, app scaffold, provider picker, CultUI tree lowerer, token mapping, and slider anatomy fixture | -| iOS / UIKit | Native proof | Renders Odin's fullscreen interface wall from `surface.root`, generic provider trees, and a custom VoidBot cockpit with avatar images | Needs provider picker, Repixelizer style token lowering, and a decision on what UIKit still owns if Flutter covers the shared graphical path | -| Android / Kotlin | Device-edge proof | APK builds and shows broker/sensor status | Needs provider picker, full surface tree rendering, style token lowering, asset image lowering, command controls, and a decision on what Kotlin still owns if Flutter covers the shared graphical path | +| Web reference | Active reference/debug renderer | `web/` renders live VoidBot and fixture surfaces through the provider picker; compiles indentation CultUI; lowers partitions, field rows, and slider anatomy; Chrome headless capture emits PNGs | Needs command round-trip tests | +| Windows / Flutter | Active shared graphical smoke target | `flutter/eve_parity` renders the CultUI Inspector surface and emits a golden PNG through `capture-flutter-parity.ps1` | Needs lowerer fed by compiled `gamecult.eve.surface.v1` instead of hardcoded inspector widget | +| Linux / Flutter | Runner-blocked shared graphical target | Flutter Linux platform scaffold exists | Needs a Linux host/runner to emit a real Linux PNG | +| iOS / UIKit | Native proof | Renders Odin's fullscreen interface wall from `surface.root`, generic provider trees, and a custom VoidBot cockpit with avatar images; SSH capture emits PNGs | Needs provider picker, Repixelizer style token lowering, and a decision on what UIKit still owns if Flutter covers the shared graphical path | +| Android / Kotlin | Device-edge proof | APK builds, Periwinkle attaches through adb, and smoke capture emits PNGs | Needs provider picker, full surface tree rendering, style token lowering, asset image lowering, command controls, and a decision on what Kotlin still owns if Flutter covers the shared graphical path | | Fensalir Direct2D | Specialized native/game-runtime landing zone | Existing `AquariumUiDocument` and `DirectWriteOverlay` path | Needs adapter from Eve surface document to `AquariumUiDocument`, DirectWrite/Direct2D token lowering, and a clear reason to stay separate from Flutter for desktop UI | ## Spawned Surfaces diff --git a/docs/runtime-app-parity-roadmap.md b/docs/runtime-app-parity-roadmap.md index 0c61eca..b8d89d4 100644 --- a/docs/runtime-app-parity-roadmap.md +++ b/docs/runtime-app-parity-roadmap.md @@ -247,3 +247,8 @@ The web reference remains the fastest behavior/debug oracle. Flutter should become the preferred graphical parity oracle if it proves the CultUI Inspector and Repixelizer fixtures can stay recognizable across its target platforms with less duplicate renderer work. + +The first shared harness is documented in +[parity-testing-harness.md](./parity-testing-harness.md). It currently checks +semantic fixture parity and records runtime capture gaps; screenshot adapters +plug into that manifest as each runtime becomes capture-capable. diff --git a/flutter/eve_parity/.gitignore b/flutter/eve_parity/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/flutter/eve_parity/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/flutter/eve_parity/.metadata b/flutter/eve_parity/.metadata new file mode 100644 index 0000000..56e62b8 --- /dev/null +++ b/flutter/eve_parity/.metadata @@ -0,0 +1,39 @@ +# 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: "924134a44c189315be2148659913dda1671cbe99" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 924134a44c189315be2148659913dda1671cbe99 + base_revision: 924134a44c189315be2148659913dda1671cbe99 + - platform: android + create_revision: 924134a44c189315be2148659913dda1671cbe99 + base_revision: 924134a44c189315be2148659913dda1671cbe99 + - platform: ios + create_revision: 924134a44c189315be2148659913dda1671cbe99 + base_revision: 924134a44c189315be2148659913dda1671cbe99 + - platform: linux + create_revision: 924134a44c189315be2148659913dda1671cbe99 + base_revision: 924134a44c189315be2148659913dda1671cbe99 + - platform: windows + create_revision: 924134a44c189315be2148659913dda1671cbe99 + base_revision: 924134a44c189315be2148659913dda1671cbe99 + + # 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' diff --git a/flutter/eve_parity/README.md b/flutter/eve_parity/README.md new file mode 100644 index 0000000..2028e1d --- /dev/null +++ b/flutter/eve_parity/README.md @@ -0,0 +1,21 @@ +# Eve Flutter Parity + +This is Eve's first Flutter parity renderer body. It exists to prove that +CultUI partitions, inspector rows, and slider anatomy can be lowered through a +shared graphical client instead of being rebuilt separately in every native +toolkit. + +Current smoke target: + +- `CultUiInspectorSurface` +- custom-painted slider track/fill/thumb +- golden PNG output through `flutter test --update-goldens` + +Run from the repository root: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\capture-flutter-parity.ps1 -Target windows +``` + +Linux capture requires running the same harness on a Linux host. The Windows +host must not pretend to be a Linux renderer. diff --git a/flutter/eve_parity/analysis_options.yaml b/flutter/eve_parity/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/flutter/eve_parity/analysis_options.yaml @@ -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 diff --git a/flutter/eve_parity/android/.gitignore b/flutter/eve_parity/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/flutter/eve_parity/android/.gitignore @@ -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 diff --git a/flutter/eve_parity/android/app/build.gradle.kts b/flutter/eve_parity/android/app/build.gradle.kts new file mode 100644 index 0000000..36062f3 --- /dev/null +++ b/flutter/eve_parity/android/app/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.eve_parity" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.eve_parity" + // 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") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/flutter/eve_parity/android/app/src/debug/AndroidManifest.xml b/flutter/eve_parity/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/flutter/eve_parity/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/flutter/eve_parity/android/app/src/main/AndroidManifest.xml b/flutter/eve_parity/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9111a10 --- /dev/null +++ b/flutter/eve_parity/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/eve_parity/android/app/src/main/kotlin/com/example/eve_parity/MainActivity.kt b/flutter/eve_parity/android/app/src/main/kotlin/com/example/eve_parity/MainActivity.kt new file mode 100644 index 0000000..047e060 --- /dev/null +++ b/flutter/eve_parity/android/app/src/main/kotlin/com/example/eve_parity/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.eve_parity + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/flutter/eve_parity/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter/eve_parity/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/flutter/eve_parity/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flutter/eve_parity/android/app/src/main/res/drawable/launch_background.xml b/flutter/eve_parity/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/flutter/eve_parity/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flutter/eve_parity/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter/eve_parity/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter/eve_parity/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter/eve_parity/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter/eve_parity/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/android/app/src/main/res/values-night/styles.xml b/flutter/eve_parity/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/flutter/eve_parity/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/flutter/eve_parity/android/app/src/main/res/values/styles.xml b/flutter/eve_parity/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/flutter/eve_parity/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/flutter/eve_parity/android/app/src/profile/AndroidManifest.xml b/flutter/eve_parity/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/flutter/eve_parity/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/flutter/eve_parity/android/build.gradle.kts b/flutter/eve_parity/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/flutter/eve_parity/android/build.gradle.kts @@ -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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/flutter/eve_parity/android/gradle.properties b/flutter/eve_parity/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/flutter/eve_parity/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/flutter/eve_parity/android/gradle/wrapper/gradle-wrapper.properties b/flutter/eve_parity/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/flutter/eve_parity/android/gradle/wrapper/gradle-wrapper.properties @@ -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-9.1.0-all.zip diff --git a/flutter/eve_parity/android/settings.gradle.kts b/flutter/eve_parity/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/flutter/eve_parity/android/settings.gradle.kts @@ -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 "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/flutter/eve_parity/ios/.gitignore b/flutter/eve_parity/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/flutter/eve_parity/ios/.gitignore @@ -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 diff --git a/flutter/eve_parity/ios/Flutter/AppFrameworkInfo.plist b/flutter/eve_parity/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/flutter/eve_parity/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/flutter/eve_parity/ios/Flutter/Debug.xcconfig b/flutter/eve_parity/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/flutter/eve_parity/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/flutter/eve_parity/ios/Flutter/Release.xcconfig b/flutter/eve_parity/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/flutter/eve_parity/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/flutter/eve_parity/ios/Runner.xcodeproj/project.pbxproj b/flutter/eve_parity/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1573b41 --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,644 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + 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; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.eveParity; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.eveParity.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.eveParity.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.eveParity.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.eveParity; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.eveParity; + 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 */ + +/* 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 = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/flutter/eve_parity/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter/eve_parity/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c3fedb2 --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/eve_parity/ios/Runner.xcworkspace/contents.xcworkspacedata b/flutter/eve_parity/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/flutter/eve_parity/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/flutter/eve_parity/ios/Runner/AppDelegate.swift b/flutter/eve_parity/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/flutter/eve_parity/ios/Runner/AppDelegate.swift @@ -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) + } +} diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" + } +} diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -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" + } +} diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -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. \ No newline at end of file diff --git a/flutter/eve_parity/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter/eve_parity/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/eve_parity/ios/Runner/Base.lproj/Main.storyboard b/flutter/eve_parity/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/eve_parity/ios/Runner/Info.plist b/flutter/eve_parity/ios/Runner/Info.plist new file mode 100644 index 0000000..01c7b8b --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Eve Parity + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + eve_parity + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/flutter/eve_parity/ios/Runner/Runner-Bridging-Header.h b/flutter/eve_parity/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/flutter/eve_parity/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/flutter/eve_parity/ios/Runner/SceneDelegate.swift b/flutter/eve_parity/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/flutter/eve_parity/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/flutter/eve_parity/ios/RunnerTests/RunnerTests.swift b/flutter/eve_parity/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/flutter/eve_parity/ios/RunnerTests/RunnerTests.swift @@ -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. + } + +} diff --git a/flutter/eve_parity/lib/main.dart b/flutter/eve_parity/lib/main.dart new file mode 100644 index 0000000..af205f7 --- /dev/null +++ b/flutter/eve_parity/lib/main.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const EveParityApp()); +} + +class EveParityApp extends StatelessWidget { + const EveParityApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + backgroundColor: Color(0xFF020909), + body: SafeArea( + child: Padding( + padding: EdgeInsets.all(16), + child: CultUiInspectorSurface(), + ), + ), + ), + ); + } +} + +class CultUiInspectorSurface extends StatelessWidget { + const CultUiInspectorSurface({super.key}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topLeft, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 1180), + child: DecoratedBox( + decoration: BoxDecoration( + color: const Color(0xEE071918), + border: Border.all(color: const Color(0x4767F0E4)), + borderRadius: BorderRadius.circular(6), + boxShadow: const [ + BoxShadow(color: Color(0x59000000), blurRadius: 26, offset: Offset(0, 12)), + ], + ), + child: const Padding( + padding: EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'AETHERIC FIELD TESTER', + style: TextStyle( + color: Color(0xFF8BA5A3), + fontFamily: 'monospace', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + SizedBox(height: 10), + InspectorSliderRow(label: 'FOV', value: 38 / 120), + SizedBox(height: 6), + InspectorSliderRow(label: 'THROTTLE DECAY', value: 5 / 20), + SizedBox(height: 6), + InspectorSliderRow(label: 'FLOW SPEED', value: 2 / 10), + ], + ), + ), + ), + ), + ); + } +} + +class InspectorSliderRow extends StatelessWidget { + const InspectorSliderRow({ + required this.label, + required this.value, + super.key, + }); + + final String label; + final double value; + + @override + Widget build(BuildContext context) { + return Container( + height: 34, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xF5201F2B), Color(0xF513141D)], + ), + border: Border.all(color: const Color(0x10FFFFFF)), + borderRadius: BorderRadius.circular(4), + boxShadow: const [ + BoxShadow(color: Color(0x66000000), blurRadius: 8, offset: Offset(0, 2)), + ], + ), + child: Row( + children: [ + SizedBox( + width: 190, + child: Text( + label, + style: const TextStyle( + color: Color(0xFFFFB84F), + fontFamily: 'monospace', + fontSize: 13, + fontWeight: FontWeight.w700, + ), + ), + ), + Expanded( + child: CultUiSlider(value: value), + ), + ], + ), + ); + } +} + +class CultUiSlider extends StatelessWidget { + const CultUiSlider({required this.value, super.key}); + + final double value; + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _SliderPainter(value.clamp(0, 1)), + child: const SizedBox(height: 22, width: double.infinity), + ); + } +} + +class _SliderPainter extends CustomPainter { + _SliderPainter(this.value); + + final double value; + + @override + void paint(Canvas canvas, Size size) { + const accent = Color(0xFFFFB84F); + final centerY = size.height / 2; + final track = RRect.fromRectAndRadius( + Rect.fromLTWH(0, centerY - 3, size.width, 6), + const Radius.circular(2), + ); + final fill = RRect.fromRectAndRadius( + Rect.fromLTWH(0, centerY - 3, size.width * value, 6), + const Radius.circular(2), + ); + final trackPaint = Paint()..color = const Color(0x57000000); + final fillPaint = Paint()..color = accent; + final thumbPaint = Paint() + ..color = accent + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3); + final thumbCorePaint = Paint()..color = accent; + + canvas.drawRRect(track, trackPaint); + canvas.drawRRect(fill, fillPaint); + final thumbCenter = Offset(size.width * value, centerY); + canvas.drawCircle(thumbCenter, 7, thumbPaint); + canvas.drawCircle(thumbCenter, 6, thumbCorePaint); + } + + @override + bool shouldRepaint(covariant _SliderPainter oldDelegate) { + return oldDelegate.value != value; + } +} diff --git a/flutter/eve_parity/linux/.gitignore b/flutter/eve_parity/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/flutter/eve_parity/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/flutter/eve_parity/linux/CMakeLists.txt b/flutter/eve_parity/linux/CMakeLists.txt new file mode 100644 index 0000000..666ba2a --- /dev/null +++ b/flutter/eve_parity/linux/CMakeLists.txt @@ -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 "eve_parity") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.eve_parity") + +# 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 "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>: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() diff --git a/flutter/eve_parity/linux/flutter/CMakeLists.txt b/flutter/eve_parity/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/flutter/eve_parity/linux/flutter/CMakeLists.txt @@ -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} +) diff --git a/flutter/eve_parity/linux/flutter/generated_plugin_registrant.cc b/flutter/eve_parity/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/flutter/eve_parity/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/flutter/eve_parity/linux/flutter/generated_plugin_registrant.h b/flutter/eve_parity/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/flutter/eve_parity/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter/eve_parity/linux/flutter/generated_plugins.cmake b/flutter/eve_parity/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/flutter/eve_parity/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + 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) diff --git a/flutter/eve_parity/linux/runner/CMakeLists.txt b/flutter/eve_parity/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/flutter/eve_parity/linux/runner/CMakeLists.txt @@ -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}") diff --git a/flutter/eve_parity/linux/runner/main.cc b/flutter/eve_parity/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/flutter/eve_parity/linux/runner/main.cc @@ -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); +} diff --git a/flutter/eve_parity/linux/runner/my_application.cc b/flutter/eve_parity/linux/runner/my_application.cc new file mode 100644 index 0000000..511b970 --- /dev/null +++ b/flutter/eve_parity/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#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, "eve_parity"); + 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, "eve_parity"); + } + + 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)); +} diff --git a/flutter/eve_parity/linux/runner/my_application.h b/flutter/eve_parity/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/flutter/eve_parity/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +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_ diff --git a/flutter/eve_parity/pubspec.lock b/flutter/eve_parity/pubspec.lock new file mode 100644 index 0000000..2450bf3 --- /dev/null +++ b/flutter/eve_parity/pubspec.lock @@ -0,0 +1,205 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" + source: hosted + version: "1.18.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.12.1 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/flutter/eve_parity/pubspec.yaml b/flutter/eve_parity/pubspec.yaml new file mode 100644 index 0000000..7e275dd --- /dev/null +++ b/flutter/eve_parity/pubspec.yaml @@ -0,0 +1,83 @@ +name: eve_parity +description: "Eve Flutter parity renderer and screenshot smoke target." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.12.1 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/flutter/eve_parity/test/goldens/cultui-inspector.png b/flutter/eve_parity/test/goldens/cultui-inspector.png new file mode 100644 index 0000000000000000000000000000000000000000..3759659e0c7042e4000715f78516e6aa03cbe383 GIT binary patch literal 82785 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:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +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) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +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. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter/eve_parity/windows/flutter/CMakeLists.txt b/flutter/eve_parity/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/flutter/eve_parity/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +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. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# 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/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app 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. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter/eve_parity/windows/flutter/generated_plugin_registrant.cc b/flutter/eve_parity/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/flutter/eve_parity/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/flutter/eve_parity/windows/flutter/generated_plugin_registrant.h b/flutter/eve_parity/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/flutter/eve_parity/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter/eve_parity/windows/flutter/generated_plugins.cmake b/flutter/eve_parity/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/flutter/eve_parity/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + 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}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter/eve_parity/windows/runner/CMakeLists.txt b/flutter/eve_parity/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/flutter/eve_parity/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +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} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# 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 build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter/eve_parity/windows/runner/Runner.rc b/flutter/eve_parity/windows/runner/Runner.rc new file mode 100644 index 0000000..9115ddb --- /dev/null +++ b/flutter/eve_parity/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "eve_parity" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "eve_parity" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "eve_parity.exe" "\0" + VALUE "ProductName", "eve_parity" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter/eve_parity/windows/runner/flutter_window.cpp b/flutter/eve_parity/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/flutter/eve_parity/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter/eve_parity/windows/runner/flutter_window.h b/flutter/eve_parity/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/flutter/eve_parity/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter/eve_parity/windows/runner/main.cpp b/flutter/eve_parity/windows/runner/main.cpp new file mode 100644 index 0000000..df32e22 --- /dev/null +++ b/flutter/eve_parity/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"eve_parity", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter/eve_parity/windows/runner/resource.h b/flutter/eve_parity/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/flutter/eve_parity/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter/eve_parity/windows/runner/resources/app_icon.ico b/flutter/eve_parity/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/flutter/eve_parity/windows/runner/runner.exe.manifest b/flutter/eve_parity/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/flutter/eve_parity/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/flutter/eve_parity/windows/runner/utils.cpp b/flutter/eve_parity/windows/runner/utils.cpp new file mode 100644 index 0000000..3cb7146 --- /dev/null +++ b/flutter/eve_parity/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + // First, find the length of the string with a safe upper bound (CWE-126). + // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. + int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); + // Now use that bounded length to determine the required buffer size. + // When an explicit length is passed, WideCharToMultiByte does not include + // the null terminator in its returned size. + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter/eve_parity/windows/runner/utils.h b/flutter/eve_parity/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/flutter/eve_parity/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter/eve_parity/windows/runner/win32_window.cpp b/flutter/eve_parity/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/flutter/eve_parity/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter/eve_parity/windows/runner/win32_window.h b/flutter/eve_parity/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/flutter/eve_parity/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/scripts/capture-android-screenshot.ps1 b/scripts/capture-android-screenshot.ps1 new file mode 100644 index 0000000..275a519 --- /dev/null +++ b/scripts/capture-android-screenshot.ps1 @@ -0,0 +1,47 @@ +param( + [string] $OutputPath = "artifacts\parity\android-periwinkle.png", + [string] $ApkPath = "artifacts\android\eve-debug.apk", + [string] $PackageName = "org.gamecult.eve", + [string] $ActivityName = ".MainActivity", + [int] $LaunchDelaySeconds = 3 +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$absoluteOutput = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path $projectRoot $OutputPath } +$absoluteApk = if ([System.IO.Path]::IsPathRooted($ApkPath)) { $ApkPath } else { Join-Path $projectRoot $ApkPath } +New-Item -ItemType Directory -Force (Split-Path -Parent $absoluteOutput) | Out-Null + +$devices = adb devices | Select-String -Pattern "device$" +if (-not $devices) { + throw "No adb device is connected for Android parity capture." +} + +if (-not (Test-Path $absoluteApk)) { + throw "Android APK not found: $absoluteApk" +} + +adb install -r $absoluteApk | Out-Host +if ($LASTEXITCODE -ne 0) { + throw "adb install failed with exit code $LASTEXITCODE" +} + +adb shell am start -n "$PackageName/$ActivityName" | Out-Host +if ($LASTEXITCODE -ne 0) { + throw "adb launch failed with exit code $LASTEXITCODE" +} + +Start-Sleep -Seconds $LaunchDelaySeconds + +$cmdOutput = $absoluteOutput.Replace('\', '\\') +cmd /c "adb exec-out screencap -p > `"$absoluteOutput`"" +if ($LASTEXITCODE -ne 0) { + throw "adb screencap failed with exit code $LASTEXITCODE" +} + +if (-not (Test-Path $absoluteOutput) -or (Get-Item $absoluteOutput).Length -le 0) { + throw "adb screencap did not produce a PNG: $absoluteOutput" +} + +Write-Host $absoluteOutput diff --git a/scripts/capture-eve-screenshot.ps1 b/scripts/capture-eve-screenshot.ps1 index 92fc1a2..4eb5d80 100644 --- a/scripts/capture-eve-screenshot.ps1 +++ b/scripts/capture-eve-screenshot.ps1 @@ -1,12 +1,17 @@ param( [string] $Target = "eve", - [string] $OutputDirectory = "artifacts\screenshots" + [string] $OutputDirectory = "artifacts\screenshots", + [string] $BundleId = "org.gamecult.evecanvas" ) $ErrorActionPreference = "Stop" $projectRoot = Split-Path -Parent $PSScriptRoot -$outputRoot = Join-Path $projectRoot $OutputDirectory +$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { + $OutputDirectory +} else { + Join-Path $projectRoot $OutputDirectory +} New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null $stamp = Get-Date -Format "yyyyMMdd-HHmmss" @@ -15,6 +20,8 @@ $remoteDir = "/var/mobile/Library/EveCanvas" $remotePath = "$remoteDir/latest-screenshot.png" $requestPath = "$remoteDir/capture-request" +ssh $Target "uiopen --bundleid '$BundleId' >/dev/null 2>&1 || true" +Start-Sleep -Seconds 1 ssh $Target "mkdir -p '$remoteDir' && chown mobile:mobile '$remoteDir' && rm -f '$remotePath' '$requestPath' && touch '$requestPath' && chown mobile:mobile '$requestPath' 2>/dev/null || true" $deadline = (Get-Date).AddSeconds(10) diff --git a/scripts/capture-flutter-parity.ps1 b/scripts/capture-flutter-parity.ps1 new file mode 100644 index 0000000..a2ebb04 --- /dev/null +++ b/scripts/capture-flutter-parity.ps1 @@ -0,0 +1,39 @@ +param( + [ValidateSet("windows", "linux")] + [string] $Target = "windows", + [string] $OutputPath = "artifacts\parity\flutter-windows-cultui-inspector.png" +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$flutterRoot = Join-Path $projectRoot "flutter\eve_parity" +$flutter = Join-Path $projectRoot "tools\deps\flutter\bin\flutter.bat" +$absoluteOutput = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path $projectRoot $OutputPath } +New-Item -ItemType Directory -Force (Split-Path -Parent $absoluteOutput) | Out-Null + +if (-not (Test-Path $flutter)) { + throw "Flutter is not installed at $flutter" +} + +if ($Target -eq "linux" -and $env:OS -like "Windows*") { + throw "Linux Flutter screenshot requires a Linux runner; this host is Windows." +} + +Push-Location $flutterRoot +try { + & $flutter test --update-goldens + if ($LASTEXITCODE -ne 0) { + throw "Flutter golden smoke failed with exit code $LASTEXITCODE" + } +} finally { + Pop-Location +} + +$golden = Join-Path $flutterRoot "test\goldens\cultui-inspector.png" +if (-not (Test-Path $golden) -or (Get-Item $golden).Length -le 0) { + throw "Flutter golden did not produce a PNG: $golden" +} + +Copy-Item -LiteralPath $golden -Destination $absoluteOutput -Force +Write-Host $absoluteOutput diff --git a/scripts/capture-web-reference.ps1 b/scripts/capture-web-reference.ps1 new file mode 100644 index 0000000..cde9fe3 --- /dev/null +++ b/scripts/capture-web-reference.ps1 @@ -0,0 +1,50 @@ +param( + [string] $ProviderId = "eve.cultui.inspector", + [string] $OutputPath = "artifacts\parity\web-cultui-inspector.png", + [int] $Port = 8891, + [int] $Width = 1280, + [int] $Height = 720 +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$absoluteOutput = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path $projectRoot $OutputPath } +New-Item -ItemType Directory -Force (Split-Path -Parent $absoluteOutput) | Out-Null + +$chrome = @( + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" +) | Where-Object { Test-Path $_ } | Select-Object -First 1 + +if (-not $chrome) { + throw "Chrome is required for web parity capture and was not found." +} + +try { + $status = (Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:$Port/" -TimeoutSec 2).StatusCode +} catch { + powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "start-browser-reference.ps1") -Port $Port | Out-Host +} + +$url = "http://127.0.0.1:$Port/?provider=$ProviderId" +$arguments = @( + "--headless=new", + "--disable-gpu", + "--hide-scrollbars", + "--window-size=$Width,$Height", + "--virtual-time-budget=2500", + "--screenshot=$absoluteOutput", + $url +) + +& $chrome @arguments | Out-Host +if ($LASTEXITCODE -ne 0) { + throw "Chrome screenshot capture failed with exit code $LASTEXITCODE" +} + +if (-not (Test-Path $absoluteOutput) -or (Get-Item $absoluteOutput).Length -le 0) { + throw "Chrome screenshot capture did not produce a PNG: $absoluteOutput" +} + +Write-Host $absoluteOutput diff --git a/scripts/run-parity-harness.ps1 b/scripts/run-parity-harness.ps1 new file mode 100644 index 0000000..ad462ff --- /dev/null +++ b/scripts/run-parity-harness.ps1 @@ -0,0 +1,19 @@ +param( + [string] $OutputDirectory = "artifacts\parity" +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$env:EVE_PARITY_OUTPUT = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { + $OutputDirectory +} else { + Join-Path $projectRoot $OutputDirectory +} + +Push-Location $projectRoot +try { + node .\tools\parity\run-parity.mjs +} finally { + Pop-Location +} diff --git a/scripts/run-parity-smoke.ps1 b/scripts/run-parity-smoke.ps1 new file mode 100644 index 0000000..634f74e --- /dev/null +++ b/scripts/run-parity-smoke.ps1 @@ -0,0 +1,116 @@ +param( + [string] $ProviderId = "eve.cultui.inspector", + [string] $OutputDirectory = "artifacts\parity-smoke", + [string] $IosSshTarget = "eve" +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$stamp = Get-Date -Format "yyyyMMdd-HHmmss" +$runRoot = Join-Path $projectRoot (Join-Path $OutputDirectory $stamp) +New-Item -ItemType Directory -Force $runRoot | Out-Null + +$results = [ordered]@{ + schema = "gamecult.eve.parity_smoke.v1" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + providerId = $ProviderId + outputDirectory = $runRoot + targets = @() +} + +function Add-TargetResult { + param( + [string] $Id, + [string] $Status, + [string] $Screenshot = "", + [string] $ErrorMessage = "" + ) + + $results.targets += [ordered]@{ + id = $Id + status = $Status + screenshot = $Screenshot + error = $ErrorMessage + } +} + +function Invoke-Capture { + param( + [string] $Id, + [scriptblock] $Capture + ) + + try { + $screenshot = & $Capture + if ($LASTEXITCODE -ne 0) { + throw "capture command exited with code $LASTEXITCODE" + } + Add-TargetResult -Id $Id -Status "pass" -Screenshot ($screenshot | Select-Object -Last 1) + } catch { + Add-TargetResult -Id $Id -Status "fail" -ErrorMessage $_.Exception.Message + } +} + +Push-Location $projectRoot +try { + powershell -ExecutionPolicy Bypass -File .\scripts\run-parity-harness.ps1 -OutputDirectory (Join-Path $runRoot "semantic") | Out-Host + if ($LASTEXITCODE -ne 0) { + throw "Semantic parity harness failed with exit code $LASTEXITCODE" + } + + Invoke-Capture "web" { + powershell -ExecutionPolicy Bypass -File .\scripts\capture-web-reference.ps1 ` + -ProviderId $ProviderId ` + -OutputPath (Join-Path $runRoot "web-$ProviderId.png") + } + + Invoke-Capture "ios" { + powershell -ExecutionPolicy Bypass -File .\scripts\capture-eve-screenshot.ps1 ` + -Target $IosSshTarget ` + -OutputDirectory $runRoot + } + + Invoke-Capture "android" { + powershell -ExecutionPolicy Bypass -File .\scripts\capture-android-screenshot.ps1 ` + -OutputPath (Join-Path $runRoot "android-periwinkle.png") + } + + Invoke-Capture "windows" { + powershell -ExecutionPolicy Bypass -File .\scripts\capture-flutter-parity.ps1 ` + -Target windows ` + -OutputPath (Join-Path $runRoot "windows-flutter-cultui-inspector.png") + } + + Invoke-Capture "linux" { + powershell -ExecutionPolicy Bypass -File .\scripts\capture-flutter-parity.ps1 ` + -Target linux ` + -OutputPath (Join-Path $runRoot "linux-flutter-cultui-inspector.png") + } + + $jsonPath = Join-Path $runRoot "parity-smoke.json" + $mdPath = Join-Path $runRoot "parity-smoke.md" + $results | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $jsonPath -Encoding UTF8 + + $lines = @( + "# Eve Parity Smoke", + "", + "Generated: $($results.generatedAt)", + "Provider: $ProviderId", + "", + "| Target | Status | Screenshot | Error |", + "| --- | --- | --- | --- |" + ) + foreach ($target in $results.targets) { + $lines += "| $($target.id) | $($target.status) | $($target.screenshot) | $($target.error) |" + } + $lines -join "`n" | Set-Content -LiteralPath $mdPath -Encoding UTF8 + + Write-Host "Parity smoke report: $mdPath" + $failed = @($results.targets | Where-Object { $_.status -ne "pass" }) + if ($failed.Count -gt 0) { + throw "$($failed.Count) parity smoke target(s) failed: $($failed.id -join ', ')" + } +} finally { + Pop-Location +} diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json new file mode 100644 index 0000000..d1a40be --- /dev/null +++ b/tools/parity/parity-manifest.json @@ -0,0 +1,133 @@ +{ + "schema": "gamecult.eve.parity_manifest.v1", + "fixtures": [ + { + "id": "cultui-inspector", + "title": "CultUI Inspector", + "surface": { + "transport": "local-eve-dsl", + "path": "web/fixtures/cultui-slider-inspector.eve" + }, + "expect": { + "providerId": "eve.cultui.inspector", + "componentKinds": ["surface", "partition", "pane", "label", "control.slider"], + "minimumCounts": { + "partition": 8, + "control.slider": 3, + "label": 3 + }, + "controlSkins": ["inspector.orangeSlider"], + "controlParts": ["track", "fill", "thumb"], + "bindings": ["tester.fov", "tester.throttleDecay", "tester.flowSpeed"] + } + }, + { + "id": "repixelizer", + "title": "Repixelizer", + "surface": { + "transport": "local-json", + "path": "web/fixtures/repixelizer.eve-surface.json" + }, + "expect": { + "providerId": "repixelizer", + "componentKinds": ["surface", "pane", "card", "text"], + "styleTokens": [ + "colorBackgroundTop", + "colorPanel", + "colorAccent", + "fontTitle", + "fontBody", + "imageRendering" + ] + } + }, + { + "id": "reactive-dsl", + "title": "Reactive DSL", + "surface": { + "transport": "local-eve-dsl", + "path": "web/fixtures/reactive-composition.eve" + }, + "expect": { + "providerId": "gamecult.eve.reactive-demo", + "componentKinds": ["surface", "card", "text", "metric", "list", "control.button"], + "bindings": ["face.name", "face.body", "swarm.heat", "swarm.eventCount", "swarm.latestEvent"] + } + }, + { + "id": "huginn-cc", + "title": "Huginn .cc", + "surface": { + "transport": "local-eve-dsl", + "path": "web/fixtures/huginn-cc-surface.eve" + }, + "expect": { + "providerId": "cultcache.huginn.inspector", + "componentKinds": ["surface", "card", "text", "list"], + "bindings": ["huginn.catalog.entries", "huginn.records.entries"] + } + } + ], + "runtimes": [ + { + "id": "web", + "title": "Web reference", + "kind": "active", + "semanticHarness": true, + "capture": { + "status": "manual-or-plugin", + "note": "Browser screenshots are currently verified through the in-app browser; a standalone Playwright/CDP runner should plug in here once the repo owns that dependency." + } + }, + { + "id": "windows-flutter", + "title": "Windows / Flutter", + "kind": "active", + "expectedPaths": ["flutter/eve_parity/lib/main.dart", "scripts/capture-flutter-parity.ps1"], + "capture": { + "status": "golden", + "note": "Uses the Flutter parity golden smoke on the Windows host." + } + }, + { + "id": "linux-flutter", + "title": "Linux / Flutter", + "kind": "requires-linux-runner", + "expectedPaths": ["flutter/eve_parity/linux", "scripts/capture-flutter-parity.ps1"], + "capture": { + "status": "blocked-on-runner", + "note": "Linux screenshot capture must run on a Linux host; Windows must fail this target rather than fake it." + } + }, + { + "id": "ios-uikit", + "title": "iOS / UIKit", + "kind": "native-proof", + "expectedPaths": ["Sources/EVEViewController.mm", "scripts/capture-eve-screenshot.ps1"], + "capture": { + "status": "ssh-png", + "note": "SSH capture script foregrounds EveCanvas and requests latest-screenshot.png." + } + }, + { + "id": "android-kotlin", + "title": "Android / Kotlin", + "kind": "device-proof", + "expectedPaths": ["android/app/src/main/java/org/gamecult/eve/MainActivity.kt", "scripts/build-android.ps1"], + "capture": { + "status": "adb-png", + "note": "adb capture installs the current APK, launches MainActivity, and pulls a screencap from Periwinkle." + } + }, + { + "id": "direct2d", + "title": "Fensalir Direct2D", + "kind": "specialized-native", + "expectedPaths": ["web/fixtures/fensalir-client-surface.json"], + "capture": { + "status": "missing", + "note": "Needs Eve surface to AquariumUiDocument adapter and Direct2D frame capture." + } + } + ] +} diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs new file mode 100644 index 0000000..592ede9 --- /dev/null +++ b/tools/parity/run-parity.mjs @@ -0,0 +1,226 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { compileEveDsl } from "../../web/eve-dsl.js"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const manifestPath = path.join(repoRoot, "tools/parity/parity-manifest.json"); +const outputRoot = process.env.EVE_PARITY_OUTPUT + ? path.resolve(process.env.EVE_PARITY_OUTPUT) + : path.join(repoRoot, "artifacts/parity"); + +const manifest = JSON.parse(await readFile(manifestPath, "utf8")); +const stamp = new Date().toISOString().replace(/[:.]/g, "-"); +const runDirectory = path.join(outputRoot, stamp); + +await mkdir(runDirectory, { recursive: true }); + +const fixtureResults = []; +for (const fixture of manifest.fixtures) { + fixtureResults.push(await evaluateFixture(fixture)); +} + +const runtimeResults = manifest.runtimes.map(evaluateRuntime); +const report = { + schema: "gamecult.eve.parity_report.v1", + generatedAt: new Date().toISOString(), + manifest: path.relative(repoRoot, manifestPath).replaceAll("\\", "/"), + summary: summarize(fixtureResults, runtimeResults), + fixtures: fixtureResults, + runtimes: runtimeResults, +}; + +await writeFile(path.join(runDirectory, "parity-report.json"), `${JSON.stringify(report, null, 2)}\n`); +await writeFile(path.join(runDirectory, "parity-report.md"), renderMarkdown(report)); +await writeFile(path.join(outputRoot, "latest.json"), `${JSON.stringify(report, null, 2)}\n`); +await writeFile(path.join(outputRoot, "latest.md"), renderMarkdown(report)); + +console.log(`Parity report: ${path.relative(repoRoot, path.join(runDirectory, "parity-report.md"))}`); +if (report.summary.failedFixtures > 0) process.exitCode = 1; + +async function evaluateFixture(fixture) { + const startedAt = Date.now(); + const state = await loadSurface(fixture.surface); + const root = state.surface?.root; + const nodes = root ? flattenSurface(root) : []; + const kindCounts = countBy(nodes, node => node.kind || "unknown"); + const bindings = [...new Set(nodes.flatMap(readBindings))].sort(); + const controlSkins = state.surface?.styles?.controlSkins || {}; + const skinNames = Object.keys(controlSkins); + const skinNodes = Object.values(controlSkins).flatMap(skin => skin.children || []); + const controlPartNames = [...new Set([...nodes, ...skinNodes] + .filter(node => node.kind === "control.part") + .map(node => node.props?.name) + .filter(Boolean))].sort(); + const tokenNames = Object.keys(state.surface?.styles?.tokens || {}).sort(); + const checks = []; + + addCheck(checks, "providerId", state.providerId === fixture.expect.providerId, { + expected: fixture.expect.providerId, + actual: state.providerId, + }); + + for (const kind of fixture.expect.componentKinds || []) { + addCheck(checks, `component:${kind}`, Boolean(kindCounts[kind]), { + expected: "present", + actual: kindCounts[kind] || 0, + }); + } + + for (const [kind, minimum] of Object.entries(fixture.expect.minimumCounts || {})) { + addCheck(checks, `minimum:${kind}`, (kindCounts[kind] || 0) >= minimum, { + expected: minimum, + actual: kindCounts[kind] || 0, + }); + } + + for (const skin of fixture.expect.controlSkins || []) { + addCheck(checks, `skin:${skin}`, skinNames.includes(skin), { + expected: "present", + actual: skinNames, + }); + } + + for (const part of fixture.expect.controlParts || []) { + addCheck(checks, `controlPart:${part}`, controlPartNames.includes(part), { + expected: "present", + actual: controlPartNames, + }); + } + + for (const token of fixture.expect.styleTokens || []) { + addCheck(checks, `styleToken:${token}`, tokenNames.includes(token), { + expected: "present", + actual: tokenNames, + }); + } + + for (const binding of fixture.expect.bindings || []) { + addCheck(checks, `binding:${binding}`, bindings.includes(binding), { + expected: "present", + actual: bindings, + }); + } + + return { + id: fixture.id, + title: fixture.title, + status: checks.every(check => check.pass) ? "pass" : "fail", + durationMs: Date.now() - startedAt, + providerId: state.providerId, + componentCount: nodes.length, + kindCounts, + styleTokens: tokenNames, + controlSkins: skinNames, + controlParts: controlPartNames, + bindings, + checks, + }; +} + +async function loadSurface(surface) { + const absolutePath = path.join(repoRoot, surface.path); + const source = await readFile(absolutePath, "utf8"); + if (surface.transport === "local-eve-dsl") return compileEveDsl(source); + if (surface.transport === "local-json") return JSON.parse(source); + throw new Error(`Unsupported parity surface transport: ${surface.transport}`); +} + +function evaluateRuntime(runtime) { + const expectedPaths = runtime.expectedPaths || []; + const missingPaths = expectedPaths.filter(candidate => !existsSync(path.join(repoRoot, candidate))); + let status = runtime.kind === "active" ? "active" : "pending"; + if (missingPaths.length) status = "missing-body"; + if (runtime.id === "web") status = "active"; + if (["ssh-png", "adb-png", "golden"].includes(runtime.capture?.status) && status === "pending") { + status = "capture-ready"; + } + return { + id: runtime.id, + title: runtime.title, + kind: runtime.kind, + status, + semanticHarness: Boolean(runtime.semanticHarness), + expectedPaths, + missingPaths, + capture: runtime.capture, + }; +} + +function flattenSurface(root) { + const nodes = []; + const visit = (node) => { + nodes.push(node); + for (const child of node.children || []) visit(child); + }; + visit(root); + return nodes; +} + +function readBindings(node) { + const props = node.props || {}; + const bindings = []; + if (props.bind) bindings.push(props.bind); + if (props.action?.target) bindings.push(props.action.target); + return bindings; +} + +function countBy(items, selector) { + const counts = {}; + for (const item of items) { + const key = selector(item); + counts[key] = (counts[key] || 0) + 1; + } + return counts; +} + +function addCheck(checks, id, pass, detail) { + checks.push({ id, pass, ...detail }); +} + +function summarize(fixtures, runtimes) { + return { + totalFixtures: fixtures.length, + passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, + failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, + totalRuntimes: runtimes.length, + activeRuntimes: runtimes.filter(runtime => runtime.status === "active").length, + pendingRuntimes: runtimes.filter(runtime => runtime.status !== "active").length, + }; +} + +function renderMarkdown(report) { + const lines = [ + "# Eve Parity Report", + "", + `Generated: ${report.generatedAt}`, + "", + "## Summary", + "", + `- Fixtures: ${report.summary.passedFixtures}/${report.summary.totalFixtures} passed`, + `- Runtimes: ${report.summary.activeRuntimes}/${report.summary.totalRuntimes} active`, + "", + "## Fixtures", + "", + "| Fixture | Status | Provider | Components | Failed Checks |", + "| --- | --- | --- | ---: | --- |", + ]; + + for (const fixture of report.fixtures) { + const failed = fixture.checks.filter(check => !check.pass).map(check => check.id).join(", ") || ""; + lines.push(`| ${fixture.title} | ${fixture.status} | ${fixture.providerId} | ${fixture.componentCount} | ${failed} |`); + } + + lines.push("", "## Runtimes", "", "| Runtime | Status | Capture | Missing Paths |", "| --- | --- | --- | --- |"); + for (const runtime of report.runtimes) { + lines.push(`| ${runtime.title} | ${runtime.status} | ${runtime.capture?.status || "unknown"} | ${runtime.missingPaths.join(", ")} |`); + } + + lines.push("", "## Runtime Notes", ""); + for (const runtime of report.runtimes) { + if (runtime.capture?.note) lines.push(`- ${runtime.title}: ${runtime.capture.note}`); + } + + return `${lines.join("\n")}\n`; +} diff --git a/web/surface.js b/web/surface.js index 2b27bf7..fea8ae8 100644 --- a/web/surface.js +++ b/web/surface.js @@ -107,7 +107,10 @@ async function bootProviders() { option.textContent = provider.title || provider.providerId; return option; })); - const firstProduct = providers.find(provider => provider.providerId === "repixelizer") || providers[0]; + const requestedProviderId = new URLSearchParams(location.search).get("provider"); + const firstProduct = providers.find(provider => provider.providerId === requestedProviderId) + || providers.find(provider => provider.providerId === "repixelizer") + || providers[0]; providerSelect.value = firstProduct.providerId; await openProvider(firstProduct); } @@ -725,10 +728,10 @@ function renderVoidBot(state) { } const left = el("section", "pane"); - left.append(el("h2", "", "Controls / Selected Face")); + left.append(el("h2", "", "Controls / Selected Persona")); left.append(el("div", "detail", `${summary?.label || "VoidBot Swarm"}\n${summary?.detail || ""}`)); left.append(metricStack()); - left.append(el("div", "detail", agent?.detail || "No selected Face detail.")); + left.append(el("div", "detail", agent?.detail || "No selected Persona detail.")); const middle = el("section", "pane"); middle.append(el("h2", "", "State Graph"));