diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 52c7231..c9939e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,50 +1,23 @@ -name: Dino Run CI - -# This workflow is triggered on push and pull request to main branch. - +name: Build on: push: branches: - main + - rewrite pull_request: branches: - main + - rewrite jobs: build: - # This job will run on ubuntu virtual machine - runs-on: ubuntu-latest + runs-on: windows-latest steps: - - # Setup Java environment in order to build the Android app. - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - # Setup the flutter environment. - - uses: subosito/flutter-action@v1 - with: - channel: 'stable' # 'dev', 'alpha', default to: 'stable' - # flutter-version: '1.12.x' # you can also specify exact version of flutter - - # Get flutter dependencies. - - run: flutter pub get - - # Check for any formatting issues in the code. - - run: flutter format --set-exit-if-changed . - - # Statically analyze the Dart code for any errors. - - run: flutter analyze . - - # Run widget tests for our flutter project. Nothing to run right now. - # - run: flutter test - - # Build apk. - - run: flutter build apk - - # Upload generated apk to the artifacts. - - uses: actions/upload-artifact@v1 - with: - name: dino-run-demo-apk - path: build/app/outputs/apk/release/app-release.apk + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: subosito/flutter-action@v2 + - run: dart format --set-exit-if-changed . + - run: flutter analyze . + - run: flutter config --enable-windows-desktop + - run: flutter build windows \ No newline at end of file diff --git a/.gitignore b/.gitignore index 957e320..fb8d88d 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,5 @@ doc/api/ # secrets secrets/ debugInfo/ -key.property \ No newline at end of file +key.property +debugInfo/ \ No newline at end of file diff --git a/.metadata b/.metadata index f0274b3..6eb54a1 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,42 @@ # This file should be version controlled and should not be manually edited. version: - revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 - channel: stable + revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: android + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: ios + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: linux + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: macos + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: web + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: windows + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + + # 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/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e8c5857 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "dino_run_shubm", + "request": "launch", + "type": "dart" + }, + { + "name": "dino_run_shubm (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "dino_run_shubm (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index cc01f92..0e446e5 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,22 @@ -
- Dino Run +# Dino Run -   +A 2D infinite side scroller made using [Flame](https://flame-engine.org/) engine. - -
+## Demo -

Dino Run

+![Menu](branding/menu.gif) -

- Github top language +![Jump](branding/jump.gif) - Github language count +![Hit](branding/hit.gif) - Repository size +## Downloads - License +- [Google Play](https://play.google.com/store/apps/details?id=com.nobs.dino_run) - Github issues +- [Itch.io](https://ufrshubham.itch.io/dino-run) - Github forks - - Github stars -

- - - -

- 🚧 Dino_run 🚀 Under construction... 🚧 -

- -
- -

- About   |   - Technologies   |   - Requirements   |   - Starting   |   - License   |   - Author -

- -
- -## :dart: About ## - -Dino Run is a 2D infinite side scroller mobile game made in Flutter. This game is made using flame package, an awesome 2D game engine for flutter. - -## :rocket: Technologies ## - -The following tools were used in this project: - -- [Flutter](https://flutter.dev/) -- [Flame](https://flame-engine.org/) - -## :white_check_mark: Requirements ## - -Before starting :checkered_flag:, you need to have [Git](https://git-scm.com) and [Flutter](https://flutter.dev/) installed. - -## :checkered_flag: Starting ## +## Build steps ```bash # Clone this project @@ -76,15 +34,44 @@ $ flutter run # The will start the game on any connected device. ``` -## :memo: License ## +## Supported platforms + +- Android +- iOS +- Windows (without audio) + +## License Assets provided in this repo are not owned by my. For more details about their origin and authors, check [assets/images/readme.md](assets/images/readme.md) and [assets/audio/readme.md](assets/audio/readme.md) This project is under license from MIT. For more details, see the [LICENSE](LICENSE) file. +## Why the code is so different than the YouTube series? + +Dino run is a simple mobile game that I originally started making, so that I can learn Flame engine. But while doing so, I couldn't find a lot of up-to date resources (apart of the official docs). So I decided to make a [YouTube series](https://www.youtube.com/playlist?list=PLiZZKL9HLmWOmQgYxWHuOHOWsUUlhCCOY) covering the development of this game. + +Everything went great and in about 7 weeks I wrapped up the series (actual game completed way sooner than the series). The original game and the YouTube series was made using version 0.28 of Flame (probably latest at that time). But little did I know that Flame was under massive development preparing for the 1.0 release. + +I suspected that my videos were going to get outdated pretty soon with newer versions of Flame. My plan to deal with this was to keep updating this repository with latest changes from Flame, so that new viewers of the series don't get stuck with a code that won't even build. But things got much worst after Flutter went null-safe. I tried migrating this project to null-safety and latest version of Flame multiple times. But I always ended up introducing a lot of bugs in the game. + +It was not like this is a very big project and a lot of people are follow it. I was easier to leave this repository in a broken state. But personally, I've been through the frustration of trying to learn something new, spending hours and hours watching some tutorial series only to find out at the end that it is completely outdated. Remaking the whole YouTube series is a big undertaking (which I am not prepared for). But rewriting this game again wasn't that big of a deal. So finally I decided to rewrite the whole thing from scratch (almost), keeping the original code on a separate branch. Surprising it took me only few hours to get it up and running. + +To check the original code, checkout the [main](https://github.com/ufrshubham/dino_run/tree/main) branch. + +## Games inspired from Dino Run + +This list might not contain all the project that are inspired from Dino Run, but these are the ones that I know of. If you want to add your game here or know of a game that should be featured here, feel free to open a pull request or send me the required details. + +- [Fly Dash](https://play.google.com/store/apps/details?id=io.madhank93.dashy_bird&hl=en_IN) by [Madhan Kumaravelu](https://github.com/madhank93) + +- [Warrior Runner](https://play.google.com/store/apps/details?id=hashim4498.games.warrior_runner&hl=en_IN) by [Mohammed Hashim](https://github.com/mohammedhashim44) + +- [DinoRun](https://github.com/DetainedDeveloper/DinoRun) by [Jay](https://github.com/DetainedDeveloper) + +- [run-dino-run](https://github.com/NextFaze/run-dino-run) by [Rushi Patel](https://github.com/whimzyLive) -Made with :brain: by Ryuzki +- [Dino Running](https://play.google.com/store/apps/details?id=rteixeira.apps.dino_run) by [Renan Teixeira](https://github.com/renant) -  +___ -Back to top +Join my [Discord](https://discord.gg/xHu3aUQGsJ) if you want to discuss something about this project. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# 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-lang.github.io/linter/lints/index.html. + # + # 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/android/.gitignore b/android/.gitignore index 0a741cb..6f56801 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -9,3 +9,5 @@ GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle index 33ab7e4..098b65f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,63 +1,58 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') +def localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> + localPropertiesFile.withReader("UTF-8") { reader -> localProperties.load(reader) } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = '1' + flutterVersionCode = "1" } -def flutterVersionName = localProperties.getProperty('flutter.versionName') +def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = '1.0' + flutterVersionName = "1.0" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 29 + namespace = "com.example.dino_run" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.dino_run" - minSdkVersion 16 - targetSdkVersion 29 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.dino_run" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName } 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.debug + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index ad092d1..399f698 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 88b2918..e073198 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,17 +1,13 @@ - - + - - @@ -44,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/dino_run/MainActivity.kt b/android/app/src/main/kotlin/com/example/dino_run/MainActivity.kt index 0d29e30..6499dc0 100644 --- a/android/app/src/main/kotlin/com/example/dino_run/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/dino_run/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.dino_run import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 1f83a33..cb1ef88 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index ad092d1..399f698 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/build.gradle b/android/build.gradle index 3100ad2..d2ffbff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,31 +1,18 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() - jcenter() + mavenCentral() } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/gradle.properties b/android/gradle.properties index a673820..3b5b324 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -android.enableR8=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 296b146..e1ca574 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..536165d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/assets/audio/8Bit Platformer Loop.wav b/assets/audio/8BitPlatformerLoop.wav similarity index 100% rename from assets/audio/8Bit Platformer Loop.wav rename to assets/audio/8BitPlatformerLoop.wav diff --git a/assets/audio/readme.md b/assets/audio/readme.md index 9317a87..72cb07a 100644 --- a/assets/audio/readme.md +++ b/assets/audio/readme.md @@ -2,7 +2,7 @@ ## BGM -- [8Bit Platformer Loop.wav](8Bit%20Platformer%20Loop.wav) +- [8BitPlatformerLoop.wav](8Bit%20Platformer%20Loop.wav) - Author: - Link: diff --git a/assets/images/parallax/plx-6.png b/assets/images/parallax/plx-6.png index 713136d..9447b80 100644 Binary files a/assets/images/parallax/plx-6.png and b/assets/images/parallax/plx-6.png differ diff --git a/branding/hit.gif b/branding/hit.gif new file mode 100644 index 0000000..7dd19c8 Binary files /dev/null and b/branding/hit.gif differ diff --git a/branding/jump.gif b/branding/jump.gif new file mode 100644 index 0000000..d2c98c1 Binary files /dev/null and b/branding/jump.gif differ diff --git a/branding/menu.gif b/branding/menu.gif new file mode 100644 index 0000000..8abfe8d Binary files /dev/null and b/branding/menu.gif differ diff --git a/ios/.gitignore b/ios/.gitignore index e96ef60..7a7f987 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f7..7c56964 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..d97f17e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 775c6b8..43c0427 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -127,7 +127,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -171,10 +171,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -185,6 +187,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -272,7 +275,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -289,15 +292,10 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -354,7 +352,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -403,11 +401,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -421,15 +420,10 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -448,15 +442,10 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a1..919434a 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..5e31d3d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Dino Run CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -41,5 +43,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/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/lib/game/audio_manager.dart b/lib/game/audio_manager.dart index e4f31ef..2450985 100644 --- a/lib/game/audio_manager.dart +++ b/lib/game/audio_manager.dart @@ -1,97 +1,57 @@ +import '/models/settings.dart'; import 'package:flame/flame.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:hive/hive.dart'; +import 'package:flame_audio/flame_audio.dart'; /// This class is the common interface between [DinoRun] /// and [Flame] engine's audio APIs. class AudioManager { + late Settings settings; AudioManager._internal(); /// [_instance] represents the single static instance of [AudioManager]. - static AudioManager _instance = AudioManager._internal(); + static final AudioManager _instance = AudioManager._internal(); /// A getter to access the single instance of [AudioManager]. static AudioManager get instance => _instance; - /// This method is responsible for initializing - /// [listenableBgm] and [listenableSfx] by reading user preferences. - /// It is also responsible for pre-caching given list of [files]. - Future init(List files) async { - Flame.bgm.initialize(); - await Flame.audio.loadAll(files); - - _pref = await Hive.openBox('preferences'); - - // On first launch of the game SFX will be null, - // so set it to true. - if (_pref.get('sfx') == null) { - _pref.put('sfx', true); - } - - // On first launch of the game BGM will be null, - // so set it to true. - if (_pref.get('bgm') == null) { - _pref.put('bgm', true); - } - - _sfx = ValueNotifier(_pref.get('sfx')); - _bgm = ValueNotifier(_pref.get('bgm')); - } - - Box _pref; - ValueNotifier _sfx; - ValueNotifier _bgm; - - // Provides value of SFX as a listenable. - ValueNotifier get listenableSfx => _sfx; - - // Provides value of BGM as a listenable. - ValueNotifier get listenableBgm => _bgm; - - // Use this method to change state of SFX. - void setSfx(bool flag) { - _pref.put('sfx', flag); - _sfx.value = flag; - } - - // Use this method to change state of BGM. - void setBgm(bool flag) { - _pref.put('bgm', flag); - _bgm.value = flag; + /// This method is responsible for initializing caching given list of [files], + /// and initilizing settings. + Future init(List files, Settings settings) async { + this.settings = settings; + FlameAudio.bgm.initialize(); + await FlameAudio.audioCache.loadAll(files); } // Starts the given audio file as BGM on loop. void startBgm(String fileName) { - if (_bgm.value) { - Flame.bgm.play(fileName, volume: 0.4); + if (settings.bgm) { + FlameAudio.bgm.play(fileName, volume: 0.4); } } // Pauses currently playing BGM if any. void pauseBgm() { - if (_bgm.value) { - Flame.bgm.pause(); + if (settings.bgm) { + FlameAudio.bgm.pause(); } } // Resumes currently paused BGM if any. void resumeBgm() { - if (_bgm.value) { - Flame.bgm.resume(); + if (settings.bgm) { + FlameAudio.bgm.resume(); } } // Stops currently playing BGM if any. void stopBgm() { - if (_bgm.value) { - Flame.bgm.stop(); - } + FlameAudio.bgm.stop(); } // Plays the given audio file once. void playSfx(String fileName) { - if (_sfx.value) { - Flame.audio.play(fileName); + if (settings.sfx) { + FlameAudio.play(fileName); } } } diff --git a/lib/game/constants.dart b/lib/game/constants.dart deleted file mode 100644 index 43d4c95..0000000 --- a/lib/game/constants.dart +++ /dev/null @@ -1,4 +0,0 @@ -const double groundHeight = 32; -const double dinoTopBottomSpacing = 10; -const int numberOfTilesAlongWidth = 10; -const double GRAVITY = 1000; diff --git a/lib/game/dino.dart b/lib/game/dino.dart index 0cce3df..4ad5551 100644 --- a/lib/game/dino.dart +++ b/lib/game/dino.dart @@ -1,148 +1,170 @@ import 'dart:ui'; -import 'package:dino_run/game/audio_manager.dart'; -import 'package:flame/anchor.dart'; -import 'package:flame/animation.dart'; -import 'package:flame/components/animation_component.dart'; -import 'package:flame/spritesheet.dart'; -import 'package:flame/time.dart'; -import 'package:flutter/foundation.dart'; - -import 'constants.dart'; - -/// This class represents the main dino character. -class Dino extends AnimationComponent { - // Holds a reference to run animation. - Animation _runAnimation; - - // Holds a reference to hit animation. - Animation _hitAnimation; - - /// This timer is used to automatically reset [animation] - /// to [_runAnimation] once [hit] is called. - Timer _timer; - - // Indicates if dino is current hit or not. - bool _isHit; +import 'package:flame/collisions.dart'; +import 'package:flame/components.dart'; + +import '/game/enemy.dart'; +import '/game/dino_run.dart'; +import '/game/audio_manager.dart'; +import '/models/player_data.dart'; + +/// This enum represents the animation states of [Dino]. +enum DinoAnimationStates { + idle, + run, + kick, + hit, + sprint, +} - // Dino's current speed along y-axis. - double speedY = 0.0; +// This represents the dino character of this game. +class Dino extends SpriteAnimationGroupComponent + with CollisionCallbacks, HasGameReference { + // A map of all the animation states and their corresponding animations. + static final _animationMap = { + DinoAnimationStates.idle: SpriteAnimationData.sequenced( + amount: 4, + stepTime: 0.1, + textureSize: Vector2.all(24), + ), + DinoAnimationStates.run: SpriteAnimationData.sequenced( + amount: 6, + stepTime: 0.1, + textureSize: Vector2.all(24), + texturePosition: Vector2((4) * 24, 0), + ), + DinoAnimationStates.kick: SpriteAnimationData.sequenced( + amount: 4, + stepTime: 0.1, + textureSize: Vector2.all(24), + texturePosition: Vector2((4 + 6) * 24, 0), + ), + DinoAnimationStates.hit: SpriteAnimationData.sequenced( + amount: 3, + stepTime: 0.1, + textureSize: Vector2.all(24), + texturePosition: Vector2((4 + 6 + 4) * 24, 0), + ), + DinoAnimationStates.sprint: SpriteAnimationData.sequenced( + amount: 7, + stepTime: 0.1, + textureSize: Vector2.all(24), + texturePosition: Vector2((4 + 6 + 4 + 3) * 24, 0), + ), + }; // The max distance from top of the screen beyond which // dino should never go. Basically the screen height - ground height double yMax = 0.0; - // This notifier keeps track of dino's remaining lives. - // It also notifies all listeners when it changes. - ValueNotifier life; - - Dino() : super.empty() { - // 0 - 3 = idle - // 4 - 10 = run - // 11 - 13 = kick - // 14 - 16 = hit - // 17 - 23 = Sprint - - /// Reads the sprite sheet for dino and creates a [SpriteSheet] object. - final spriteSheet = SpriteSheet( - imageName: 'DinoSprites - tard.png', - textureWidth: 24, - textureHeight: 24, - columns: 24, - rows: 1, - ); - - _runAnimation = - spriteSheet.createAnimation(0, from: 4, to: 10, stepTime: 0.1); + // Dino's current speed along y-axis. + double speedY = 0.0; - _hitAnimation = - spriteSheet.createAnimation(0, from: 14, to: 16, stepTime: 0.1); + // Controlls how long the hit animations will be played. + final Timer _hitTimer = Timer(1); - // By default Dino will be running. - this.animation = _runAnimation; + static const double gravity = 800; - /// Calls [run] method after 1 second - /// everytime [_timer.start] is called. - _timer = Timer(1, callback: () { - run(); - }); - _isHit = false; + final PlayerData playerData; - // This makes sure that origin of dino is at its center, instead of top-left corner. - this.anchor = Anchor.center; + bool isHit = false; - life = ValueNotifier(5); - } + Dino(Image image, this.playerData) + : super.fromFrameData(image, _animationMap); @override - void resize(Size size) { - super.resize(size); + void onMount() { + // First reset all the important properties, because onMount() + // will be called even while restarting the game. + _reset(); + + // Add a hitbox for dino. + add( + RectangleHitbox.relative( + Vector2(0.5, 0.7), + parentSize: size, + position: Vector2(size.x * 0.5, size.y * 0.3) / 2, + ), + ); + yMax = y; - /// Resizes dino sprite such that exactly [numberOfTilesAlongWidth] number of - /// dinos can fix horizontally. - this.height = this.width = size.width / numberOfTilesAlongWidth; + /// Set the callback for [_hitTimer]. + _hitTimer.onTick = () { + current = DinoAnimationStates.run; + isHit = false; + }; - this.x = this.width; - this.y = - size.height - groundHeight - (this.height / 2) + dinoTopBottomSpacing; - this.yMax = this.y; + super.onMount(); } @override - void update(double t) { - super.update(t); + void update(double dt) { // v = u + at - this.speedY += GRAVITY * t; + speedY += gravity * dt; // d = s0 + s * t - this.y += this.speedY * t; + y += speedY * dt; /// This code makes sure that dino never goes beyond [yMax]. - if (isOnGround()) { - this.y = this.yMax; - this.speedY = 0.0; + if (isOnGround) { + y = yMax; + speedY = 0.0; + if ((current != DinoAnimationStates.hit) && + (current != DinoAnimationStates.run)) { + current = DinoAnimationStates.run; + } } - // NOTE: Never forget to update the timer, because Flame's timer - // depends on gameloop's update for its calculations. - _timer.update(t); - } - - // Returns true if dino is on ground. - bool isOnGround() { - return (this.y >= this.yMax); - } - - /// Sets [animation] to [_runAnimation] - void run() { - _isHit = false; - this.animation = _runAnimation; + _hitTimer.update(dt); + super.update(dt); } - /// Sets [animation] to [_hitAnimation] - void hit() { - // Ignore if already in hit state. - if (!_isHit) { - _isHit = true; - this.animation = _hitAnimation; - - // Reduce life by 1. - life.value -= 1; - - AudioManager.instance.playSfx('hurt7.wav'); - - /// Start the timer so that animation is reset to [_runAnimation] - /// after 1 seconds. - _timer.start(); + // Gets called when dino collides with other Collidables. + @override + void onCollision(Set intersectionPoints, PositionComponent other) { + // Call hit only if other component is an Enemy and dino + // is not already in hit state. + if ((other is Enemy) && (!isHit)) { + hit(); } + super.onCollision(intersectionPoints, other); } + // Returns true if dino is on ground. + bool get isOnGround => (y >= yMax); + // Makes the dino jump. void jump() { // Jump only if dino is on ground. - if (isOnGround()) { - this.speedY = -500; + if (isOnGround) { + speedY = -300; + current = DinoAnimationStates.idle; AudioManager.instance.playSfx('jump14.wav'); } } + + // This method changes the animation state to + /// [DinoAnimationStates.hit], plays the hit sound + /// effect and reduces the player life by 1. + void hit() { + isHit = true; + AudioManager.instance.playSfx('hurt7.wav'); + current = DinoAnimationStates.hit; + _hitTimer.start(); + playerData.lives -= 1; + } + + // This method reset some of the important properties + // of this component back to normal. + void _reset() { + if (isMounted) { + removeFromParent(); + } + anchor = Anchor.bottomLeft; + position = Vector2(32, game.virtualSize.y - 22); + size = Vector2.all(24); + current = DinoAnimationStates.run; + isHit = false; + speedY = 0.0; + } } diff --git a/lib/game/dino_run.dart b/lib/game/dino_run.dart new file mode 100644 index 0000000..9512c6e --- /dev/null +++ b/lib/game/dino_run.dart @@ -0,0 +1,204 @@ +import 'package:flame/events.dart'; +import 'package:flame/flame.dart'; +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:hive/hive.dart'; +import 'package:flame/parallax.dart'; +import 'package:flutter/material.dart'; +import 'package:flame/components.dart'; + +import '/game/dino.dart'; +import '/widgets/hud.dart'; +import '/models/settings.dart'; +import '/game/audio_manager.dart'; +import '/game/enemy_manager.dart'; +import '/models/player_data.dart'; +import '/widgets/pause_menu.dart'; +import '/widgets/game_over_menu.dart'; + +// This is the main flame game class. +class DinoRun extends FlameGame with TapDetector, HasCollisionDetection { + DinoRun({super.camera}); + + // List of all the image assets. + static const _imageAssets = [ + 'DinoSprites - tard.png', + 'AngryPig/Walk (36x30).png', + 'Bat/Flying (46x30).png', + 'Rino/Run (52x34).png', + 'parallax/plx-1.png', + 'parallax/plx-2.png', + 'parallax/plx-3.png', + 'parallax/plx-4.png', + 'parallax/plx-5.png', + 'parallax/plx-6.png', + ]; + + // List of all the audio assets. + static const _audioAssets = [ + '8BitPlatformerLoop.wav', + 'hurt7.wav', + 'jump14.wav', + ]; + + late Dino _dino; + late Settings settings; + late PlayerData playerData; + late EnemyManager _enemyManager; + + Vector2 get virtualSize => camera.viewport.virtualSize; + + // This method get called while flame is preparing this game. + @override + Future onLoad() async { + // Makes the game full screen and landscape only. + await Flame.device.fullScreen(); + await Flame.device.setLandscape(); + + /// Read [PlayerData] and [Settings] from hive. + playerData = await _readPlayerData(); + settings = await _readSettings(); + + /// Initilize [AudioManager]. + await AudioManager.instance.init(_audioAssets, settings); + + // Start playing background music. Internally takes care + // of checking user settings. + AudioManager.instance.startBgm('8BitPlatformerLoop.wav'); + + // Cache all the images. + await images.loadAll(_imageAssets); + + // This makes the camera look at the center of the viewport. + camera.viewfinder.position = camera.viewport.virtualSize * 0.5; + + /// Create a [ParallaxComponent] and add it to game. + final parallaxBackground = await loadParallaxComponent( + [ + ParallaxImageData('parallax/plx-1.png'), + ParallaxImageData('parallax/plx-2.png'), + ParallaxImageData('parallax/plx-3.png'), + ParallaxImageData('parallax/plx-4.png'), + ParallaxImageData('parallax/plx-5.png'), + ParallaxImageData('parallax/plx-6.png'), + ], + baseVelocity: Vector2(10, 0), + velocityMultiplierDelta: Vector2(1.4, 0), + ); + + // Add the parallax as the backdrop. + camera.backdrop.add(parallaxBackground); + } + + /// This method add the already created [Dino] + /// and [EnemyManager] to this game. + void startGamePlay() { + _dino = Dino(images.fromCache('DinoSprites - tard.png'), playerData); + _enemyManager = EnemyManager(); + + world.add(_dino); + world.add(_enemyManager); + } + + // This method remove all the actors from the game. + void _disconnectActors() { + _dino.removeFromParent(); + _enemyManager.removeAllEnemies(); + _enemyManager.removeFromParent(); + } + + // This method reset the whole game world to initial state. + void reset() { + // First disconnect all actions from game world. + _disconnectActors(); + + // Reset player data to inital values. + playerData.currentScore = 0; + playerData.lives = 5; + } + + // This method gets called for each tick/frame of the game. + @override + void update(double dt) { + // If number of lives is 0 or less, game is over. + if (playerData.lives <= 0) { + overlays.add(GameOverMenu.id); + overlays.remove(Hud.id); + pauseEngine(); + AudioManager.instance.pauseBgm(); + } + super.update(dt); + } + + // This will get called for each tap on the screen. + @override + void onTapDown(TapDownInfo info) { + // Make dino jump only when game is playing. + // When game is in playing state, only Hud will be the active overlay. + if (overlays.isActive(Hud.id)) { + _dino.jump(); + } + super.onTapDown(info); + } + + /// This method reads [PlayerData] from the hive box. + Future _readPlayerData() async { + final playerDataBox = + await Hive.openBox('DinoRun.PlayerDataBox'); + final playerData = playerDataBox.get('DinoRun.PlayerData'); + + // If data is null, this is probably a fresh launch of the game. + if (playerData == null) { + // In such cases store default values in hive. + await playerDataBox.put('DinoRun.PlayerData', PlayerData()); + } + + // Now it is safe to return the stored value. + return playerDataBox.get('DinoRun.PlayerData')!; + } + + /// This method reads [Settings] from the hive box. + Future _readSettings() async { + final settingsBox = await Hive.openBox('DinoRun.SettingsBox'); + final settings = settingsBox.get('DinoRun.Settings'); + + // If data is null, this is probably a fresh launch of the game. + if (settings == null) { + // In such cases store default values in hive. + await settingsBox.put( + 'DinoRun.Settings', + Settings(bgm: true, sfx: true), + ); + } + + // Now it is safe to return the stored value. + return settingsBox.get('DinoRun.Settings')!; + } + + @override + void lifecycleStateChange(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + // On resume, if active overlay is not PauseMenu, + // resume the engine (lets the parallax effect play). + if (!(overlays.isActive(PauseMenu.id)) && + !(overlays.isActive(GameOverMenu.id))) { + resumeEngine(); + } + break; + case AppLifecycleState.paused: + case AppLifecycleState.detached: + case AppLifecycleState.inactive: + case AppLifecycleState.hidden: + // If game is active, then remove Hud and add PauseMenu + // before pausing the game. + if (overlays.isActive(Hud.id)) { + overlays.remove(Hud.id); + overlays.add(PauseMenu.id); + } + pauseEngine(); + break; + } + super.lifecycleStateChange(state); + } +} diff --git a/lib/game/enemy.dart b/lib/game/enemy.dart index e682ba4..d4413a4 100644 --- a/lib/game/enemy.dart +++ b/lib/game/enemy.dart @@ -1,144 +1,54 @@ -import 'dart:math'; -import 'dart:ui'; - -import 'package:flame/anchor.dart'; -import 'package:flame/components/animation_component.dart'; -import 'package:flame/spritesheet.dart'; -import 'package:flutter/foundation.dart'; - -import 'constants.dart'; - -// This enum defines all the enemy types. -enum EnemyType { AngryPig, Bat, Rino } - -/// This class holds all the necessary data for creation of an [Enemy]. -class EnemyData { - // Path to sprite sheet of this enemy. - final String imageName; - - /// Width of a single sprite from the sprite sheet located at [imageName]. - final int textureWidth; - - /// Height of a single sprite from the sprite sheet located at [imageName]. - final int textureHeight; - - /// Number of columns in sprite sheet located at [imageName]. - final int nColumns; - - /// Number of rows in sprite sheet located at [imageName]. - final int nRows; - - /// Indicates if this enemy can fly. If true, spawns this enemy - /// at two different height randomly. - final bool canFly; - - // Speed along x direction. - final int speed; - - const EnemyData({ - @required this.imageName, - @required this.textureWidth, - @required this.textureHeight, - @required this.nColumns, - @required this.nRows, - @required this.canFly, - @required this.speed, - }); -} - -// This class represents enemy characters. -class Enemy extends AnimationComponent { - /// Reference to [EnemyData] used for creation of this enemy. - EnemyData _myData; - - /// A shared random generator for all the [Enemy]s. - static Random _random = Random(); - - /// A shared constant map of [EnemyType] and their corresponding [EnemyData]. - static const Map _enemyDetails = { - EnemyType.AngryPig: EnemyData( - imageName: 'AngryPig/Walk (36x30).png', - nColumns: 16, - nRows: 1, - textureHeight: 30, - textureWidth: 36, - canFly: false, - speed: 250, - ), - EnemyType.Bat: EnemyData( - imageName: 'Bat/Flying (46x30).png', - nColumns: 7, - nRows: 1, - textureHeight: 30, - textureWidth: 46, - canFly: true, - speed: 300, - ), - EnemyType.Rino: EnemyData( - imageName: 'Rino/Run (52x34).png', - nColumns: 6, - nRows: 1, - textureHeight: 34, - textureWidth: 52, - canFly: false, - speed: 350, - ), - }; - - Enemy(EnemyType enemyType) : super.empty() { - /// First get reference to correct [EnemyData]. - _myData = _enemyDetails[enemyType]; - - final spriteSheet = SpriteSheet( - imageName: _myData.imageName, - textureWidth: _myData.textureWidth, - textureHeight: _myData.textureHeight, - columns: _myData.nColumns, - rows: _myData.nRows, +import 'package:flame/collisions.dart'; +import 'package:flame/components.dart'; + +import '/game/dino_run.dart'; +import '/models/enemy_data.dart'; + +// This represents an enemy in the game world. +class Enemy extends SpriteAnimationComponent + with CollisionCallbacks, HasGameReference { + // The data required for creation of this enemy. + final EnemyData enemyData; + + Enemy(this.enemyData) { + animation = SpriteAnimation.fromFrameData( + enemyData.image, + SpriteAnimationData.sequenced( + amount: enemyData.nFrames, + stepTime: enemyData.stepTime, + textureSize: enemyData.textureSize, + ), ); - - this.animation = spriteSheet.createAnimation(0, - from: 0, to: (_myData.nColumns - 1), stepTime: 0.1); - - // Makes sure that origin is at center of enemy sprite. - this.anchor = Anchor.center; } @override - void resize(Size size) { - super.resize(size); - - /// The scale factor to be multiplied with [this.width] to make it - /// equal to dino's width. - double scaleFactor = - (size.width / numberOfTilesAlongWidth) / _myData.textureWidth; - - /// Resizes dino sprite such that exactly [numberOfTilesAlongWidth] number of - /// enemies can fix horizontally. - this.height = _myData.textureHeight * scaleFactor; - this.width = _myData.textureWidth * scaleFactor; - - /// Places enemy a little off screen on right end and just above on ground. - this.x = size.width + this.width; - this.y = size.height - groundHeight - (this.height / 2); - - /// If this enemy can fly, place it randomly along y-axis. - if (_myData.canFly && _random.nextBool()) { - this.y -= this.height; - } + void onMount() { + // Reduce the size of enemy as they look too + // big compared to the dino. + size *= 0.6; + + // Add a hitbox for this enemy. + add( + RectangleHitbox.relative( + Vector2.all(0.8), + parentSize: size, + position: Vector2(size.x * 0.2, size.y * 0.2) / 2, + ), + ); + super.onMount(); } @override - void update(double t) { - super.update(t); - this.x -= _myData.speed * t; - } + void update(double dt) { + position.x -= enemyData.speedX * dt; + + // Remove the enemy and increase player score + // by 1, if enemy has gone past left end of the screen. + if (position.x < -enemyData.textureSize.x) { + removeFromParent(); + game.playerData.currentScore += 1; + } - // This method is used by the flame engine to check if this component should be destroyed. - @override - bool destroy() { - // Let the framework know that this enemy can be destroyed - // once it goes off screen from left end. - return (this.x < (-this.width)); + super.update(dt); } } diff --git a/lib/game/enemy_manager.dart b/lib/game/enemy_manager.dart index 8c23687..9594973 100644 --- a/lib/game/enemy_manager.dart +++ b/lib/game/enemy_manager.dart @@ -1,80 +1,103 @@ import 'dart:math'; -import 'dart:ui'; -import 'package:dino_run/game/enemy.dart'; -import 'package:dino_run/game/game.dart'; -import 'package:flame/components/component.dart'; -import 'package:flame/components/mixins/has_game_ref.dart'; -import 'package:flame/time.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:flame/components.dart'; + +import '/game/enemy.dart'; +import '/game/dino_run.dart'; +import '/models/enemy_data.dart'; // This class is responsible for spawning random enemies at certain // interval of time depending upon players current score. -class EnemyManager extends Component with HasGameRef { - /// Random generator required for randomly selecting [EnemyType] - Random _random; +class EnemyManager extends Component with HasGameReference { + // A list to hold data for all the enemies. + final List _data = []; - // Timer to decide when to spawn next enemy. - Timer _timer; + // Random generator required for randomly selecting enemy type. + final Random _random = Random(); - // This indicates how aggressively enemies are spawned. - int _spawnLevel; + // Timer to decide when to spawn next enemy. + final Timer _timer = Timer(2, repeat: true); EnemyManager() { - _random = Random(); - _spawnLevel = 0; - _timer = Timer(4, repeat: true, callback: () { - spawnRandomEnemy(); - }); + _timer.onTick = spawnRandomEnemy; } - /// This method is responsible for spawning an enemy of random [EnemyType]. + // This method is responsible for spawning a random enemy. void spawnRandomEnemy() { - /// Get a random integer from 0 to number of [EnemyType]s -1. - final randomNumber = _random.nextInt(EnemyType.values.length); - final randomEnemyType = EnemyType.values.elementAt(randomNumber); - final newEnemy = Enemy(randomEnemyType); - gameRef.addLater(newEnemy); + /// Generate a random index within [_data] and get an [EnemyData]. + final randomIndex = _random.nextInt(_data.length); + final enemyData = _data.elementAt(randomIndex); + final enemy = Enemy(enemyData); + + // Help in setting all enemies on ground. + enemy.anchor = Anchor.bottomLeft; + enemy.position = Vector2( + game.virtualSize.x + 32, + game.virtualSize.y - 24, + ); + + // If this enemy can fly, set its y position randomly. + if (enemyData.canFly) { + final newHeight = _random.nextDouble() * 2 * enemyData.textureSize.y; + enemy.position.y -= newHeight; + } + + // Due to the size of our viewport, we can + // use textureSize as size for the components. + enemy.size = enemyData.textureSize; + game.world.add(enemy); } - /// This method starts the [_timer]. It get called when this [EnemyManager] - /// gets added to an [Game] instance. @override void onMount() { - super.onMount(); + if (isMounted) { + removeFromParent(); + } + + // Don't fill list again and again on every mount. + if (_data.isEmpty) { + // As soon as this component is mounted, initilize all the data. + _data.addAll([ + EnemyData( + image: game.images.fromCache('AngryPig/Walk (36x30).png'), + nFrames: 16, + stepTime: 0.1, + textureSize: Vector2(36, 30), + speedX: 80, + canFly: false, + ), + EnemyData( + image: game.images.fromCache('Bat/Flying (46x30).png'), + nFrames: 7, + stepTime: 0.1, + textureSize: Vector2(46, 30), + speedX: 100, + canFly: true, + ), + EnemyData( + image: game.images.fromCache('Rino/Run (52x34).png'), + nFrames: 6, + stepTime: 0.09, + textureSize: Vector2(52, 34), + speedX: 150, + canFly: false, + ), + ]); + } _timer.start(); + super.onMount(); } - /// Not needed for [EnemyManager] but [Component] class forces to implement it. @override - void render(Canvas c) {} - - @override - void update(double t) { - _timer.update(t); - - /// This increases [_spawnLevel] by 1, every 500 score points. - var newSpawnLevel = (gameRef.score ~/ 500); - if (_spawnLevel < newSpawnLevel) { - _spawnLevel = newSpawnLevel; - - // y = 4 / (1 + 0.1 * x) - var newWaitTime = (4 / (1 + (0.1 * _spawnLevel))); - - _timer.stop(); - _timer = Timer(newWaitTime, repeat: true, callback: () { - spawnRandomEnemy(); - }); - _timer.start(); - } + void update(double dt) { + _timer.update(dt); + super.update(dt); } - /// This method is responsible for resetting the [_spawnLevel] and [_timer] - void reset() { - _spawnLevel = 0; - _timer = Timer(4, repeat: true, callback: () { - spawnRandomEnemy(); - }); - _timer.start(); + void removeAllEnemies() { + final enemies = game.world.children.whereType(); + for (var enemy in enemies) { + enemy.removeFromParent(); + } } } diff --git a/lib/game/game.dart b/lib/game/game.dart deleted file mode 100644 index 3a5dbf8..0000000 --- a/lib/game/game.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'package:dino_run/game/audio_manager.dart'; -import 'package:dino_run/game/dino.dart'; -import 'package:dino_run/game/enemy.dart'; -import 'package:dino_run/game/enemy_manager.dart'; -import 'package:dino_run/widgets/game_over_menu.dart'; -import 'package:dino_run/widgets/hud.dart'; -import 'package:dino_run/widgets/pause_menu.dart'; -import 'package:flame/components/parallax_component.dart'; -import 'package:flame/components/text_component.dart'; -import 'package:flame/game/base_game.dart'; -import 'package:flame/game/game.dart'; -import 'package:flame/gestures.dart'; -import 'package:flame/position.dart'; -import 'package:flame/text_config.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; - -// This is the main game class. -class DinoGame extends BaseGame with TapDetector, HasWidgetsOverlay { - // This variable stores the current score of player. - int score; - - // A temp variable for score calculations. - double _elapsedTime = 0.0; - - // This is the main character component. - Dino _dino; - - /// This component displays the [score] at top-center of the screen. - TextComponent _scoreText; - - // This component is responsible for spawning random enemies. - EnemyManager _enemyManager; - - // This component creates the moving parallax background. - ParallaxComponent _parallaxComponent; - - // These flags help to detect current state of the game. - bool _isGameOver = false; - bool _isGamePaused = false; - - /// This default constructor is responsible for creating all the components - /// and adding them to [DinoGame]'s components list. - DinoGame() { - /// Last [ParallaxImage] uses [LayerFill.none] so that ground does not - /// take up the whole screen. - _parallaxComponent = ParallaxComponent( - [ - ParallaxImage('parallax/plx-1.png'), - ParallaxImage('parallax/plx-2.png'), - ParallaxImage('parallax/plx-3.png'), - ParallaxImage('parallax/plx-4.png'), - ParallaxImage('parallax/plx-5.png'), - ParallaxImage('parallax/plx-6.png', fill: LayerFill.none), - ], - baseSpeed: Offset(100, 0), - layerDelta: Offset(20, 0), - ); - add(_parallaxComponent); - - _dino = Dino(); - add(_dino); - - _enemyManager = EnemyManager(); - add(_enemyManager); - - score = 0; - _scoreText = TextComponent( - score.toString(), - config: TextConfig(fontFamily: 'Audiowide', color: Colors.white), - ); - add(_scoreText); - - // This adds the pause button on top-left corner and - // life indicators on top-right corner. - addWidgetOverlay( - 'Hud', - HUD( - life: _dino.life, - onPausePressed: pauseGame, - ), - ); - - AudioManager.instance.startBgm('8Bit Platformer Loop.wav'); - } - - @override - void resize(Size size) { - super.resize(size); - - /// This makes sure that [_scoreText] is placed at the top-center of the screen. - _scoreText.setByPosition( - Position(((size.width / 2) - (_scoreText.width / 2)), 0)); - } - - /// This method comes from [TapDetector] mixin and it gets called everytime - /// the screen is tapped. - @override - void onTapDown(TapDownDetails details) { - super.onTapDown(details); - if (!_isGameOver && !_isGamePaused) { - _dino.jump(); - } - } - - @override - void update(double t) { - super.update(t); - - /// Here [60] is the rate of increase of score. - _elapsedTime += t; - if (_elapsedTime > (1 / 60)) { - _elapsedTime = 0.0; - score += 1; - _scoreText.text = score.toString(); - } - - /// This code is responsible for hit detection of [_dino] - /// with all the [Enemy]'s current in game world. - components.whereType().forEach((enemy) { - if (_dino.distance(enemy) < 30) { - _dino.hit(); - } - }); - - // If dino runs out of lives, game is over. - if (_dino.life.value <= 0) { - gameOver(); - } - } - - // This method helps in detecting changes in app's life cycle state - // and pause the game if it becomes inactive. - @override - void lifecycleStateChange(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.resumed: - break; - case AppLifecycleState.inactive: - this.pauseGame(); - break; - case AppLifecycleState.paused: - this.pauseGame(); - break; - case AppLifecycleState.detached: - this.pauseGame(); - break; - } - } - - // This method pauses the game. - void pauseGame() { - pauseEngine(); - - if (!_isGameOver) { - _isGamePaused = true; - // Adds the pause menu. - addWidgetOverlay( - 'PauseMenu', - PauseMenu( - onResumePressed: resumeGame, - ), - ); - } - AudioManager.instance.pauseBgm(); - } - - // This method resumes the game. - void resumeGame() { - // First remove any pause menu and then resume the engine. - removeWidgetOverlay('PauseMenu'); - - _isGamePaused = false; - - resumeEngine(); - AudioManager.instance.resumeBgm(); - } - - // This method display the game over menu. - void gameOver() { - // First pause the game. - pauseEngine(); - - _isGameOver = true; - - // Adds the game over menu. - addWidgetOverlay( - 'GameOverMenu', - GameOverMenu( - score: score, - onRestartPressed: reset, - ), - ); - AudioManager.instance.pauseBgm(); - } - - // This method takes care of resetting all the game data to initial state. - void reset() { - this.score = 0; - _dino.life.value = 5; - _dino.run(); - - // Let enemy manager know that game needs to reset. - _enemyManager.reset(); - - // Removes all the enemies currently in the game world. - components.whereType().forEach( - (enemy) { - this.markToRemove(enemy); - }, - ); - - removeWidgetOverlay('GameOverMenu'); - _isGameOver = false; - resumeEngine(); - AudioManager.instance.resumeBgm(); - } - - @override - void onDetach() { - AudioManager.instance.stopBgm(); - super.onDetach(); - } -} diff --git a/lib/main.dart b/lib/main.dart index 781e240..4bd522b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,41 +1,83 @@ -import 'package:dino_run/game/audio_manager.dart'; -import 'package:dino_run/screens/main_menu.dart'; -import 'package:flame/flame.dart'; -import 'package:flutter/material.dart'; +import 'package:flame/camera.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; +import 'package:flame/game.dart'; +import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; +import 'widgets/hud.dart'; +import 'game/dino_run.dart'; +import 'models/settings.dart'; +import 'widgets/main_menu.dart'; +import 'models/player_data.dart'; +import 'widgets/pause_menu.dart'; +import 'widgets/settings_menu.dart'; +import 'widgets/game_over_menu.dart'; Future main() async { - await performInitialSetup(); - runApp(MyApp()); -} - -// This function is responsible for setting up -// full-screen, landscape mode, user preferences -// and pre-caching of audio. -Future performInitialSetup() async { WidgetsFlutterBinding.ensureInitialized(); - await Flame.util.fullScreen(); - await Flame.util.setLandscape(); - - final dir = await getApplicationDocumentsDirectory(); - Hive.init(dir.path); + await initHive(); + runApp(const DinoRunApp()); +} - await AudioManager.instance - .init(['8Bit Platformer Loop.wav', 'hurt7.wav', 'jump14.wav']); +Future initHive() async { + if (!kIsWeb) { + final dir = await getApplicationDocumentsDirectory(); + Hive.init(dir.path); + } + Hive.registerAdapter(PlayerDataAdapter()); + Hive.registerAdapter(SettingsAdapter()); } -class MyApp extends StatelessWidget { +class DinoRunApp extends StatelessWidget { + const DinoRunApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( + debugShowCheckedModeBanner: false, title: 'Dino Run', theme: ThemeData( fontFamily: 'Audiowide', primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, + // Settings up some default theme for elevated buttons. + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 10.0), + fixedSize: const Size(200, 60), + ), + ), + ), + home: Scaffold( + body: GameWidget.controlled( + // This will dislpay a loading bar until [DinoRun] completes + // its onLoad method. + loadingBuilder: (conetxt) => const Center( + child: SizedBox( + width: 200, + child: LinearProgressIndicator(), + ), + ), + // Register all the overlays that will be used by this game. + overlayBuilderMap: { + MainMenu.id: (_, game) => MainMenu(game), + PauseMenu.id: (_, game) => PauseMenu(game), + Hud.id: (_, game) => Hud(game), + GameOverMenu.id: (_, game) => GameOverMenu(game), + SettingsMenu.id: (_, game) => SettingsMenu(game), + }, + // By default MainMenu overlay will be active. + initialActiveOverlays: const [MainMenu.id], + gameFactory: () => DinoRun( + // Use a fixed resolution camera to avoid manually + // scaling and handling different screen sizes. + camera: CameraComponent.withFixedResolution( + width: 360, + height: 180, + ), + ), + ), ), - home: MainMenu(), ); } } diff --git a/lib/models/enemy_data.dart b/lib/models/enemy_data.dart new file mode 100644 index 0000000..9f08179 --- /dev/null +++ b/lib/models/enemy_data.dart @@ -0,0 +1,21 @@ +import 'package:flame/extensions.dart'; + +// This class stores all the data +// necessary for creation of an enemy. +class EnemyData { + final Image image; + final int nFrames; + final double stepTime; + final Vector2 textureSize; + final double speedX; + final bool canFly; + + const EnemyData({ + required this.image, + required this.nFrames, + required this.stepTime, + required this.textureSize, + required this.speedX, + required this.canFly, + }); +} diff --git a/lib/models/player_data.dart b/lib/models/player_data.dart new file mode 100644 index 0000000..80b58ed --- /dev/null +++ b/lib/models/player_data.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; + +part 'player_data.g.dart'; + +// This class stores the player progress presistently. +@HiveType(typeId: 0) +class PlayerData extends ChangeNotifier with HiveObjectMixin { + @HiveField(1) + int highScore = 0; + + int _lives = 5; + + int get lives => _lives; + set lives(int value) { + if (value <= 5 && value >= 0) { + _lives = value; + notifyListeners(); + } + } + + int _currentScore = 0; + + int get currentScore => _currentScore; + set currentScore(int value) { + _currentScore = value; + + if (highScore < _currentScore) { + highScore = _currentScore; + } + + notifyListeners(); + save(); + } +} diff --git a/lib/models/player_data.g.dart b/lib/models/player_data.g.dart new file mode 100644 index 0000000..10d4e93 --- /dev/null +++ b/lib/models/player_data.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_data.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class PlayerDataAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + PlayerData read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return PlayerData()..highScore = fields[1] as int; + } + + @override + void write(BinaryWriter writer, PlayerData obj) { + writer + ..writeByte(1) + ..writeByte(1) + ..write(obj.highScore); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PlayerDataAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/settings.dart b/lib/models/settings.dart new file mode 100644 index 0000000..ca091ef --- /dev/null +++ b/lib/models/settings.dart @@ -0,0 +1,33 @@ +import 'package:hive/hive.dart'; +import 'package:flutter/foundation.dart'; + +part 'settings.g.dart'; + +// This class stores the game settings persistently. +@HiveType(typeId: 1) +class Settings extends ChangeNotifier with HiveObjectMixin { + Settings({bool bgm = false, bool sfx = false}) { + _bgm = bgm; + _sfx = sfx; + } + + @HiveField(0) + bool _bgm = false; + + bool get bgm => _bgm; + set bgm(bool value) { + _bgm = value; + notifyListeners(); + save(); + } + + @HiveField(1) + bool _sfx = false; + + bool get sfx => _sfx; + set sfx(bool value) { + _sfx = value; + notifyListeners(); + save(); + } +} diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart new file mode 100644 index 0000000..35a4c6d --- /dev/null +++ b/lib/models/settings.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'settings.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class SettingsAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + Settings read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Settings() + .._bgm = fields[0] as bool + .._sfx = fields[1] as bool; + } + + @override + void write(BinaryWriter writer, Settings obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj._bgm) + ..writeByte(1) + ..write(obj._sfx); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SettingsAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/screens/game_play.dart b/lib/screens/game_play.dart deleted file mode 100644 index 83cc0a1..0000000 --- a/lib/screens/game_play.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:dino_run/game/game.dart'; -import 'package:flutter/material.dart'; - -/// This class is responsible for creating an instance of [DinoGame] -/// and returning its widget. -class GamePlay extends StatelessWidget { - final DinoGame _dinoGame = DinoGame(); - - GamePlay({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: _dinoGame.widget, - ); - } -} diff --git a/lib/screens/main_menu.dart b/lib/screens/main_menu.dart deleted file mode 100644 index 4448046..0000000 --- a/lib/screens/main_menu.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:dino_run/widgets/menu.dart'; -import 'package:dino_run/widgets/settings.dart'; -import 'package:flutter/material.dart'; - -// This class represents the main menu of this game. -class MainMenu extends StatefulWidget { - const MainMenu({Key key}) : super(key: key); - - @override - _MainMenuState createState() => _MainMenuState(); -} - -class _MainMenuState extends State { - /// This notifier is used to decide which menu should be displayed - /// from [Menu] or [Settings]. - ValueNotifier _crossFadeStateNotifier; - - @override - void initState() { - super.initState(); - _crossFadeStateNotifier = ValueNotifier(CrossFadeState.showFirst); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/images/background.png'), - fit: BoxFit.cover, - ), - ), - child: Center( - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - ), - color: Colors.black.withOpacity(0.4), - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 100.0, vertical: 50.0), - child: ValueListenableBuilder( - valueListenable: _crossFadeStateNotifier, - builder: - (BuildContext context, CrossFadeState value, Widget child) { - return AnimatedCrossFade( - crossFadeState: value, - duration: Duration(milliseconds: 300), - firstChild: Menu( - onSettingsPressed: showSettings, - ), - secondChild: Settings( - onBackPressed: showMenu, - ), - ); - }, - ), - ), - ), - ), - ), - ); - } - - /// When called will change the current menu to [Menu]. - void showMenu() { - _crossFadeStateNotifier.value = CrossFadeState.showFirst; - } - - /// When called will change the current menu to [Settings]. - void showSettings() { - _crossFadeStateNotifier.value = CrossFadeState.showSecond; - } -} diff --git a/lib/widgets/game_over_menu.dart b/lib/widgets/game_over_menu.dart index 614698c..4d6a2d8 100644 --- a/lib/widgets/game_over_menu.dart +++ b/lib/widgets/game_over_menu.dart @@ -1,82 +1,95 @@ -import 'package:dino_run/screens/main_menu.dart'; +import 'dart:ui'; + import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '/widgets/hud.dart'; +import '/game/dino_run.dart'; +import '/widgets/main_menu.dart'; +import '/models/player_data.dart'; +import '/game/audio_manager.dart'; -// Builds the game over menu for this game. +// This represents the game over overlay, +// displayed with dino runs out of lives. class GameOverMenu extends StatelessWidget { - // Score to display on game over menu. - final int score; + // An unique identified for this overlay. + static const id = 'GameOverMenu'; - // This function will be called when restart button is pressed. - final Function onRestartPressed; + // Reference to parent game. + final DinoRun game; - const GameOverMenu({ - Key key, - @required this.score, - @required this.onRestartPressed, - }) : assert(score != null), - assert(onRestartPressed != null), - super(key: key); + const GameOverMenu(this.game, {super.key}); @override Widget build(BuildContext context) { - return Center( - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - ), - color: Colors.black.withOpacity(0.5), - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: 100.0, - vertical: 50.0, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Game Over', - style: TextStyle(fontSize: 30.0, color: Colors.white), - ), - Text( - 'Your score was $score', - style: TextStyle(fontSize: 30.0, color: Colors.white), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(right: 2), - child: RaisedButton( - child: Text( - 'Retry', - style: TextStyle(fontSize: 30.0), + return ChangeNotifierProvider.value( + value: game.playerData, + child: Center( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Card( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + color: Colors.black.withAlpha(100), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 100), + child: Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10, + children: [ + const Text( + 'Game Over', + style: TextStyle(fontSize: 40, color: Colors.white), + ), + Selector( + selector: (_, playerData) => playerData.currentScore, + builder: (_, score, __) { + return Text( + 'You Score: $score', + style: const TextStyle( + fontSize: 40, color: Colors.white), + ); + }, + ), + ElevatedButton( + child: const Text( + 'Restart', + style: TextStyle( + fontSize: 30, + ), ), onPressed: () { - onRestartPressed.call(); + game.overlays.remove(GameOverMenu.id); + game.overlays.add(Hud.id); + game.resumeEngine(); + game.reset(); + game.startGamePlay(); + AudioManager.instance.resumeBgm(); }, ), - ), - Padding( - padding: const EdgeInsets.only(left: 2), - child: RaisedButton( - child: Text( - 'Main Menu', - style: TextStyle(fontSize: 30.0), + ElevatedButton( + child: const Text( + 'Exit', + style: TextStyle( + fontSize: 30, + ), ), onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => MainMenu(), - ), - ); + game.overlays.remove(GameOverMenu.id); + game.overlays.add(MainMenu.id); + game.resumeEngine(); + game.reset(); + AudioManager.instance.resumeBgm(); }, ), - ) - ], - ) - ], + ], + ), + ), + ), ), ), ), diff --git a/lib/widgets/hud.dart b/lib/widgets/hud.dart index be41dec..9a3dd63 100644 --- a/lib/widgets/hud.dart +++ b/lib/widgets/hud.dart @@ -1,58 +1,87 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -// Builds the heads-up display for this game. -class HUD extends StatelessWidget { - // This function will be called when pause button is pressed. - final Function onPausePressed; +import '/game/dino_run.dart'; +import '/game/audio_manager.dart'; +import '/models/player_data.dart'; +import '/widgets/pause_menu.dart'; - // A value notifier to update the health bar. - final ValueNotifier life; +// This represents the head up display in game. +// It consists of, current score, high score, +// a pause button and number of remaining lives. +class Hud extends StatelessWidget { + // An unique identified for this overlay. + static const id = 'Hud'; - const HUD({ - Key key, - @required this.onPausePressed, - @required this.life, - }) : assert(onPausePressed != null), - assert(life != null), - super(key: key); + // Reference to parent game. + final DinoRun game; + + const Hud(this.game, {super.key}); @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon( - Icons.pause, - color: Colors.white, - size: 30.0, - ), - onPressed: () { - onPausePressed.call(); - }, - ), - ValueListenableBuilder( - valueListenable: life, - builder: (BuildContext context, value, Widget child) { - final list = List(); - - // This loop decides how many hearts are filled and how many are empty - // depending upon the current dino life. - for (int i = 0; i < 5; ++i) { - list.add( - Icon( - (i < value) ? Icons.favorite : Icons.favorite_border, - color: Colors.red, + return ChangeNotifierProvider.value( + value: game.playerData, + child: Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + Selector( + selector: (_, playerData) => playerData.currentScore, + builder: (_, score, __) { + return Text( + 'Score: $score', + style: const TextStyle(fontSize: 20, color: Colors.white), + ); + }, ), - ); - } - - return Row( - children: list, - ); - }, - ) - ], + Selector( + selector: (_, playerData) => playerData.highScore, + builder: (_, highScore, __) { + return Text( + 'High: $highScore', + style: const TextStyle(color: Colors.white), + ); + }, + ), + ], + ), + TextButton( + onPressed: () { + game.overlays.remove(Hud.id); + game.overlays.add(PauseMenu.id); + game.pauseEngine(); + AudioManager.instance.pauseBgm(); + }, + child: const Icon(Icons.pause, color: Colors.white), + ), + Selector( + selector: (_, playerData) => playerData.lives, + builder: (_, lives, __) { + return Row( + children: List.generate(5, (index) { + if (index < lives) { + return const Icon( + Icons.favorite, + color: Colors.red, + ); + } else { + return const Icon( + Icons.favorite_border, + color: Colors.red, + ); + } + }), + ); + }, + ) + ], + ), + ), ); } } diff --git a/lib/widgets/main_menu.dart b/lib/widgets/main_menu.dart new file mode 100644 index 0000000..7b6671c --- /dev/null +++ b/lib/widgets/main_menu.dart @@ -0,0 +1,78 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '/widgets/hud.dart'; +import '/game/dino_run.dart'; +import '/widgets/settings_menu.dart'; + +// This represents the main menu overlay. +class MainMenu extends StatelessWidget { + // An unique identified for this overlay. + static const id = 'MainMenu'; + + // Reference to parent game. + final DinoRun game; + + const MainMenu(this.game, {super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Card( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + color: Colors.black.withAlpha(100), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 100), + child: Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10, + children: [ + const Text( + 'Dino Run', + style: TextStyle( + fontSize: 50, + color: Colors.white, + ), + ), + ElevatedButton( + onPressed: () { + game.startGamePlay(); + game.overlays.remove(MainMenu.id); + game.overlays.add(Hud.id); + }, + child: const Text( + 'Play', + style: TextStyle( + fontSize: 30, + ), + ), + ), + ElevatedButton( + onPressed: () { + game.overlays.remove(MainMenu.id); + game.overlays.add(SettingsMenu.id); + }, + child: const Text( + 'Settings', + style: TextStyle( + fontSize: 30, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart deleted file mode 100644 index 64304bb..0000000 --- a/lib/widgets/menu.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:dino_run/screens/game_play.dart'; -import 'package:flutter/material.dart'; - -/// This class displays the first menu on [MainMenu] -/// with play and settings buttons. -class Menu extends StatelessWidget { - final Function onSettingsPressed; - - const Menu({ - Key key, - @required this.onSettingsPressed, - }) : assert(onSettingsPressed != null), - super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Dino Run', - style: TextStyle(fontSize: 60.0, color: Colors.white), - ), - RaisedButton( - child: Text( - 'Play', - style: TextStyle(fontSize: 30.0), - ), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => GamePlay(), - ), - ); - }, - ), - RaisedButton( - child: Text( - 'Settings', - style: TextStyle(fontSize: 30.0), - ), - onPressed: onSettingsPressed, - ) - ], - ); - } -} diff --git a/lib/widgets/pause_menu.dart b/lib/widgets/pause_menu.dart index 511909d..56fb0dc 100644 --- a/lib/widgets/pause_menu.dart +++ b/lib/widgets/pause_menu.dart @@ -1,49 +1,107 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '/widgets/hud.dart'; +import '/game/dino_run.dart'; +import '/widgets/main_menu.dart'; +import '/game/audio_manager.dart'; +import '/models/player_data.dart'; -// Builds the pause menu for this game. +// This represents the pause menu overlay. class PauseMenu extends StatelessWidget { - // This function will be called when resume - // button is pressed. - final Function onResumePressed; + // An unique identified for this overlay. + static const id = 'PauseMenu'; - const PauseMenu({ - Key key, - @required this.onResumePressed, - }) : assert(onResumePressed != null), - super(key: key); + // Reference to parent game. + final DinoRun game; + + const PauseMenu(this.game, {super.key}); @override Widget build(BuildContext context) { - return Center( - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - ), - color: Colors.black.withOpacity(0.5), - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: 100.0, - vertical: 50.0, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Paused', - style: TextStyle(fontSize: 30.0, color: Colors.white), - ), - IconButton( - icon: Icon( - Icons.play_arrow, - color: Colors.white, - size: 30.0, + return ChangeNotifierProvider.value( + value: game.playerData, + child: Center( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Card( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + color: Colors.black.withAlpha(100), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 100), + child: Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10, + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Selector( + selector: (_, playerData) => playerData.currentScore, + builder: (_, score, __) { + return Text( + 'Score: $score', + style: const TextStyle( + fontSize: 40, color: Colors.white), + ); + }, + ), + ), + ElevatedButton( + onPressed: () { + game.overlays.remove(PauseMenu.id); + game.overlays.add(Hud.id); + game.resumeEngine(); + AudioManager.instance.resumeBgm(); + }, + child: const Text( + 'Resume', + style: TextStyle( + fontSize: 30, + ), + ), + ), + ElevatedButton( + onPressed: () { + game.overlays.remove(PauseMenu.id); + game.overlays.add(Hud.id); + game.resumeEngine(); + game.reset(); + game.startGamePlay(); + AudioManager.instance.resumeBgm(); + }, + child: const Text( + 'Restart', + style: TextStyle( + fontSize: 30, + ), + ), + ), + ElevatedButton( + onPressed: () { + game.overlays.remove(PauseMenu.id); + game.overlays.add(MainMenu.id); + game.resumeEngine(); + game.reset(); + AudioManager.instance.resumeBgm(); + }, + child: const Text( + 'Exit', + style: TextStyle( + fontSize: 30, + ), + ), + ), + ], ), - onPressed: () { - onResumePressed.call(); - }, - ) - ], + ), + ), ), ), ), diff --git a/lib/widgets/settings.dart b/lib/widgets/settings.dart deleted file mode 100644 index cd35c6a..0000000 --- a/lib/widgets/settings.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:dino_run/game/audio_manager.dart'; -import 'package:flutter/material.dart'; - -/// This class displays the settings menu on [MainMenu]. -class Settings extends StatelessWidget { - final Function onBackPressed; - - const Settings({ - Key key, - @required this.onBackPressed, - }) : assert(onBackPressed != null), - super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - width: 300.0, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Settings', - style: TextStyle(fontSize: 60.0, color: Colors.white), - ), - ValueListenableBuilder( - valueListenable: AudioManager.instance.listenableSfx, - builder: (BuildContext context, bool isSfxOn, Widget child) { - return SwitchListTile( - value: isSfxOn, - title: Text( - 'SFX', - style: TextStyle(fontSize: 30.0, color: Colors.white), - ), - onChanged: (bool value) { - AudioManager.instance.setSfx(value); - }, - ); - }, - ), - ValueListenableBuilder( - valueListenable: AudioManager.instance.listenableBgm, - builder: (BuildContext context, bool isBgmOn, Widget child) { - return SwitchListTile( - value: isBgmOn, - title: Text( - 'BGM', - style: TextStyle(fontSize: 30.0, color: Colors.white), - ), - onChanged: (bool value) { - AudioManager.instance.setBgm(value); - }, - ); - }, - ), - IconButton( - icon: Icon( - Icons.arrow_back_ios_rounded, - size: 30.0, - color: Colors.white, - ), - onPressed: onBackPressed, - ), - ], - ), - ); - } -} diff --git a/lib/widgets/settings_menu.dart b/lib/widgets/settings_menu.dart new file mode 100644 index 0000000..09a3c03 --- /dev/null +++ b/lib/widgets/settings_menu.dart @@ -0,0 +1,101 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '/game/dino_run.dart'; +import '/models/settings.dart'; +import '/widgets/main_menu.dart'; +import '/game/audio_manager.dart'; + +// This represents the settings menu overlay. +class SettingsMenu extends StatelessWidget { + // An unique identified for this overlay. + static const id = 'SettingsMenu'; + + // Reference to parent game. + final DinoRun game; + + const SettingsMenu(this.game, {super.key}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: game.settings, + child: Center( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.8, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + color: Colors.black.withAlpha(100), + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 100), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Selector( + selector: (_, settings) => settings.bgm, + builder: (context, bgm, __) { + return SwitchListTile( + title: const Text( + 'Music', + style: TextStyle( + fontSize: 30, + color: Colors.white, + ), + ), + value: bgm, + onChanged: (bool value) { + Provider.of(context, listen: false).bgm = + value; + if (value) { + AudioManager.instance + .startBgm('8BitPlatformerLoop.wav'); + } else { + AudioManager.instance.stopBgm(); + } + }, + ); + }, + ), + Selector( + selector: (_, settings) => settings.sfx, + builder: (context, sfx, __) { + return SwitchListTile( + title: const Text( + 'Effects', + style: TextStyle( + fontSize: 30, + color: Colors.white, + ), + ), + value: sfx, + onChanged: (bool value) { + Provider.of(context, listen: false).sfx = + value; + }, + ); + }, + ), + TextButton( + onPressed: () { + game.overlays.remove(SettingsMenu.id); + game.overlays.add(MainMenu.id); + }, + child: const Icon(Icons.arrow_back_ios_rounded), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..7f96dc2 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +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 "dino_run") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.dino_run") + +# 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) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# 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 dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# 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/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/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/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..1830e5c --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/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/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..e9abb91 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + 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/linux/main.cc b/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/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/linux/my_application.cc b/linux/my_application.cc new file mode 100644 index 0000000..adf71cf --- /dev/null +++ b/linux/my_application.cc @@ -0,0 +1,124 @@ +#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) + +// 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, "dino_run"); + 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, "dino_run"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + 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); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), 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() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/my_application.h b/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/linux/my_application.h @@ -0,0 +1,18 @@ +#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/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..a9f2f23 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import audioplayers_darwin +import path_provider_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..a3f71fc --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,29 @@ +PODS: + - audioplayers_darwin (0.0.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + +EXTERNAL SOURCES: + audioplayers_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + +SPEC CHECKSUMS: + audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.13.0 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7bc176a --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 9F243834B89F6339503D1AD0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46ED3C5DB20F0276BEF3D17C /* Pods_RunnerTests.framework */; }; + DC5FF0E59E8F91730CD2BD4D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 920AAB211625E94023C0B13B /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0A044D5BEA773ABC1AF85D98 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 0D9AF252BC650D5EEC5917AF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 0DAC98CD6E2154D58F6F849D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* dino_run.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dino_run.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 46ED3C5DB20F0276BEF3D17C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51362E2E980342FBB81D4C38 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 920AAB211625E94023C0B13B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A80B0BF730498BD0B22072B2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + ACBCC8810C2ED732E776A02E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F243834B89F6339503D1AD0 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC5FF0E59E8F91730CD2BD4D /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + D5C73EF954832F07119CDE07 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* dino_run.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D5C73EF954832F07119CDE07 /* Pods */ = { + isa = PBXGroup; + children = ( + ACBCC8810C2ED732E776A02E /* Pods-Runner.debug.xcconfig */, + 0D9AF252BC650D5EEC5917AF /* Pods-Runner.release.xcconfig */, + 0A044D5BEA773ABC1AF85D98 /* Pods-Runner.profile.xcconfig */, + 51362E2E980342FBB81D4C38 /* Pods-RunnerTests.debug.xcconfig */, + A80B0BF730498BD0B22072B2 /* Pods-RunnerTests.release.xcconfig */, + 0DAC98CD6E2154D58F6F849D /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 920AAB211625E94023C0B13B /* Pods_Runner.framework */, + 46ED3C5DB20F0276BEF3D17C /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + C34E76F8A2D641B7D48F58BD /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1312AE67E48D12D4E843B4C9 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + AAD866F91E78D3BD77FF1DCE /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* dino_run.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1312AE67E48D12D4E843B4C9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + AAD866F91E78D3BD77FF1DCE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C34E76F8A2D641B7D48F58BD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51362E2E980342FBB81D4C38 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dino_run.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dino_run"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A80B0BF730498BD0B22072B2 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dino_run.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dino_run"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0DAC98CD6E2154D58F6F849D /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dino_run.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dino_run"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..94c83e4 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..d00eb86 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = dino_run + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.dinoRun + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +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/pubspec.yaml b/pubspec.yaml index 9af48b2..56613bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,53 +1,32 @@ name: dino_run -description: A new Flutter project. +description: A 2D infinite side scroller. -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" -# 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 used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=3.3.4 <4.0.0' dependencies: - flame: ^0.28.0 - hive: ^1.4.4+1 - path_provider: ^1.6.27 + flame: 1.17.0 + flame_audio: 2.10.1 + hive: 2.2.3 + path_provider: 2.1.3 + provider: 6.1.2 flutter: sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.0 + cupertino_icons: 1.0.8 dev_dependencies: + hive_generator: 2.0.1 + build_runner: 2.4.10 flutter_test: sdk: flutter + flutter_lints: 4.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. 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: - assets/images/ - assets/images/parallax/ @@ -56,26 +35,7 @@ flutter: - assets/images/Rino/ - assets/audio/ - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # 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: Audiowide fonts: - asset: assets/fonts/Audiowide/Audiowide-Regular.ttf - # - 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/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart index fcc09eb..3c6f5a6 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:dino_run/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const DinoRunApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..22e5535 --- /dev/null +++ b/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + dino_run + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..649e9e8 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "dino_run", + "short_name": "dino_run", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..8abd782 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.14) +project(dino_run LANGUAGES CXX) + +set(BINARY_NAME "dino_run") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + 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() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +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() + +# 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/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..4f2af69 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,108 @@ +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/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..09e8e2c --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/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/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..375535c --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows +) + +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/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..de2d891 --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +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_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..64f35c9 --- /dev/null +++ b/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", "dino_run" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "dino_run" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "dino_run.exe" "\0" + VALUE "ProductName", "dino_run" "\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/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b43b909 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#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()); + 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/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/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/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..78fa671 --- /dev/null +++ b/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.CreateAndShow(L"dino_run", 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/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/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/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..c977c4a --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..d19bdbb --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#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(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/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/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..c10f08d --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// 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 registar 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::CreateAndShow(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 | WS_VISIBLE, + 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; + } + + return OnCreate(); +} + +// 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; + } + + 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. +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..17ba431 --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#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 and shows a win32 window with |title| and position and size 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 to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // 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 + // responsponds 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; + + 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_