diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..369f2bf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Lint, test, and security audit + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Verify Flutter installation + run: flutter --version + + - name: Run CI checks + run: ./tool/ci.sh diff --git a/.github/workflows/release-android.yml b/.github/workflows/release-android.yml new file mode 100644 index 0000000..f159823 --- /dev/null +++ b/.github/workflows/release-android.yml @@ -0,0 +1,130 @@ +# Build signed Android release artifacts and optionally deploy to Google Play. +# Requires repository secrets (see README "Release builds"). +name: Release Android + +on: + workflow_dispatch: + inputs: + deploy_track: + description: Play Store track (none = build only) + required: true + default: none + type: choice + options: + - none + - internal + - alpha + - beta + - production + +concurrency: + group: release-android-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + name: Build AAB and APK + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Configure Android signing + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + ANDROID_KEY_STORE_PASSWORD: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + run: | + if [ -z "$ANDROID_KEYSTORE_BASE64" ]; then + echo "Missing ANDROID_KEYSTORE_BASE64 secret" >&2 + exit 1 + fi + echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/upload-keystore.jks" + { + echo "storePassword=$ANDROID_KEY_STORE_PASSWORD" + echo "keyPassword=$ANDROID_KEY_PASSWORD" + echo "keyAlias=$ANDROID_KEY_ALIAS" + echo "storeFile=$RUNNER_TEMP/upload-keystore.jks" + } > android/key.properties + + - name: Install Fastlane dependencies + working-directory: android + run: | + gem install bundler + bundle install + + - name: Build release artifacts + working-directory: android + run: bundle exec fastlane build_android production:true + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: android-release + retention-days: 7 + path: | + build/app/outputs/flutter-apk/app-release.apk + build/app/outputs/bundle/release/app-release.aab + + deploy: + name: Deploy to Play Store + needs: build + if: ${{ inputs.deploy_track != 'none' }} + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: android-release + path: build/app/outputs + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.2" + bundler-cache: true + working-directory: android + + - name: Write Play Store credentials + env: + PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }} + run: | + if [ -z "$PLAY_STORE_JSON_KEY" ]; then + echo "Missing PLAY_STORE_JSON_KEY secret" >&2 + exit 1 + fi + echo "$PLAY_STORE_JSON_KEY" > "$RUNNER_TEMP/play-store.json" + + - name: Deploy via Fastlane + working-directory: android + env: + SUPPLY_JSON_KEY: ${{ runner.temp }}/play-store.json + run: | + track="${{ inputs.deploy_track }}" + case "$track" in + internal) bundle exec fastlane deploy_android internal:true ;; + alpha) bundle exec fastlane deploy_android alpha:true ;; + beta) bundle exec fastlane deploy_android beta:true ;; + production) bundle exec fastlane deploy_android production:true ;; + *) echo "Unknown track: $track" >&2; exit 1 ;; + esac diff --git a/.gitignore b/.gitignore index 2109e1d..47c9b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,9 +52,11 @@ ios/fastlane/metadata/review_information ios/fastlane/metadata/trade_representative_contact_information # Found over time -*.pbxproj *.sqbpro *.lock +# Flutter-generated iOS tooling (regenerated by flutter run/build) +ios/Flutter/ephemeral/ + # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 90fd2e1..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,161 +0,0 @@ -# Possible upgrade from -#https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a -stages: - - update - - test - - build - - deploy - -# -# Common tracks -# -tests: - stage: test - tags: - - flutter - script: - - flutter test - interruptible: true - -update: - stage: update - tags: - - flutter - script: - - flutter packages get - - flutter packages upgrade - interruptible: true - - -# -# android specifics -# - -android:build: - stage: build - tags: - - flutter - # only: - # - master - script: - # Flutter local configuration - - echo flutter.sdk=$FLUTTER_PATH > android/local.properties - - echo sdk.dir=$ANDROID_SDK_PATH >> android/local.properties - - echo flutter.buildMode=release >> android/local.properties - # Android signing - - echo storePassword=$ANDROID_KEY_STORE_PASSWORD > android/key.properties - - echo keyPassword=$ANDROID_KEY_PASSWORD >> android/key.properties - - echo keyAlias=$ANDROID_KEY_ALIAS >> android/key.properties - - echo storeFile=$ANDROID_KEYSTORE_PATH >> android/key.properties - - cd android - - gem install bundler - - bundle install - - bundle update --bundler - - bundle exec fastlane build_android production:true - - rm -f android/local.properties - - rm -f android/key.properties - artifacts: - paths: - - build/app/outputs/flutter-apk/app-release.apk - - build/app/outputs/bundle/release/app-release.aab - untracked: true - expire_in: 1 week - interruptible: true - -internal:android:deploy: - stage: deploy - tags: - - flutter - # only: - # - master - script: - - cd android - - bundle exec fastlane deploy_android internal:true - when: manual - dependencies: - - android:build - -alpha:android:deploy: - stage: deploy - tags: - - flutter - # only: - # - master - script: - - cd android - - bundle exec fastlane deploy_android alpha:true - when: manual - dependencies: - - android:build - -beta:android:deploy: - stage: deploy - tags: - - flutter - # only: - # - master - script: - - cd android - - bundle exec fastlane deploy_android beta:true - when: manual - dependencies: - - android:build - -prod:android:deploy: - stage: deploy - tags: - - flutter - only: - - master - script: - - cd android - - bundle exec fastlane deploy_android production:true - when: manual - dependencies: - - android:build - - -# -# ios specifics -# - -# ios:build: -# stage: build -# tags: -# - flutter -# script: -# - cd ios -# - export TEMP_KEYCHAIN_NAME=fastlane_$(cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w ${1:-16} | head -n 1) -# - export TEMP_KEYCHAIN_PASSWORD=$(cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w ${1:-64} | head -n 1) -# - bundle exec fastlane build_ios -# artifacts: -# paths: -# - ios/Runner.ipa -# expire_in: 1 week -# interruptible: true - -# prod:ios:deploy: -# stage: deploy -# tags: -# - flutter -# only: -# - master -# script: -# - cd ios -# - bundle exec fastlane deploy_ios -# when: manual -# dependencies: -# - ios:build - -# z:tflight:ios:deploy: -# stage: deploy -# tags: -# - flutter -# only: -# - master -# script: -# - cd ios -# - bundle exec fastlane deploy_ios testflight:true -# when: manual -# dependencies: -# - android:build \ No newline at end of file diff --git a/.metadata b/.metadata index a57aed9..89db5de 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,42 @@ # This file should be version controlled and should not be manually edited. version: - revision: 6eaaf1650e671f9bbc6f34fba003cc5729f365b2 - channel: master + revision: "ff37bef603469fb030f2b72995ab929ccfc227f0" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + - platform: android + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + - platform: ios + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + - platform: linux + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + - platform: macos + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + - platform: web + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + - platform: windows + create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0 + + # 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/README.md b/README.md index 3fdecda..8641f31 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,18 @@ flutter run ### Run tests and static analysis +Run the full CI pipeline locally (format check, analyze, tests, dependency audit): + +```bash +./tool/ci.sh +``` + +Individual steps: + ```bash -flutter analyze +flutter pub get +dart format --output=none --set-exit-if-changed lib test +dart analyze --fatal-infos flutter test ``` @@ -126,9 +136,35 @@ dart run flutter_launcher_icons ## Continuous integration -CI/CD runs on GitLab CI (see [`.gitlab-ci.yml`](.gitlab-ci.yml)) across `test`, `build` -and `deploy` stages. A self‑hosted `gitlab-runner` tagged `flutter` with the Flutter SDK, -Android SDK and signing keys is required. Deployment uses Fastlane. +CI runs on [GitHub Actions](https://github.com/features/actions). + +### Checks (every push and PR to `main`) + +Workflow: [`.github/workflows/ci.yml`](.github/workflows/ci.yml) + +- Dependency install, format check, static analysis, and tests +- Same steps as [`tool/ci.sh`](tool/ci.sh) — run that script locally before pushing + +### Android release builds and Play Store deploy + +Workflow: [`.github/workflows/release-android.yml`](.github/workflows/release-android.yml) + +Triggered manually from the GitHub **Actions** tab (**Run workflow**). Choose **none** to build +only and download APK/AAB artifacts from the run; pick a Play Store track to deploy after the +build. + +Configure these [repository secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions): + +| Secret | Purpose | +| ------ | ------- | +| `ANDROID_KEYSTORE_BASE64` | Release keystore file, base64-encoded | +| `ANDROID_KEY_STORE_PASSWORD` | Keystore password | +| `ANDROID_KEY_PASSWORD` | Key password | +| `ANDROID_KEY_ALIAS` | Key alias | +| `PLAY_STORE_JSON_KEY` | Google Play service account JSON (for deploy only) | + +Build and upload use Fastlane lanes in [`android/fastlane`](android/fastlane) (`build_android`, +`deploy_android`). ## License diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..38b7715 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,13 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - "**/*.g.dart" + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + use_super_parameters: true 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/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 5ee5355..496614f 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 76c4400..87b4a75 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index f8f0774..d343a5d 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index a6ef266..4dbb52e 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 9d5e757..8dd911c 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ 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/fastlane/Fastfile b/android/fastlane/Fastfile index e2eca5b..383d8bc 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -1,5 +1,4 @@ -# Based on: -# https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a +# Android release lanes used by GitHub Actions (see .github/workflows/release-android.yml). # Update fastlane automatically update_fastlane @@ -9,7 +8,7 @@ default_platform(:android) platform :android do desc "Build android aab and apk" lane :build_android do |options| - # Run tests (deactivated, already done in gitlab-ci) + # Tests run in CI before release builds (see .github/workflows/ci.yml) # sh "./flutter_test.sh" # Clean before building sh "./flutter_build.sh --clean" diff --git a/android/fastlane/flutter_build.sh b/android/fastlane/flutter_build.sh index 71d338b..e63986a 100755 --- a/android/fastlane/flutter_build.sh +++ b/android/fastlane/flutter_build.sh @@ -1,7 +1,6 @@ #!/bin/bash -# Based on: -# https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a +# Flutter release build helper for Fastlane (see android/fastlane/Fastfile). cd ../../ if [ "$1" == "--clean" ] diff --git a/android/fastlane/flutter_test.sh b/android/fastlane/flutter_test.sh index f74d5dd..b9c2dde 100755 --- a/android/fastlane/flutter_test.sh +++ b/android/fastlane/flutter_test.sh @@ -1,7 +1,6 @@ #!/bin/bash -# Based on: -# https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a +# Flutter test helper for Fastlane (see android/fastlane/Fastfile). cd ../../ echo "Running tests" diff --git a/assets/locales/en.yaml b/assets/locales/en.yaml index d1a7d17..266f211 100644 --- a/assets/locales/en.yaml +++ b/assets/locales/en.yaml @@ -2,6 +2,9 @@ title: 'Passive Agressive Generator' home: + tone_mid: 'Spicy' + tap_hint: 'Tap the card or button for another note' + new_note: 'New note' add_quote: 'Drop a 💩' thumb_up: 'This 💩 rocks! 👍' thumb_up_label: 'Awesome' diff --git a/assets/locales/fr.yaml b/assets/locales/fr.yaml index c62c723..e657cf2 100644 --- a/assets/locales/fr.yaml +++ b/assets/locales/fr.yaml @@ -2,6 +2,9 @@ title: 'Passive Agressive Generator' home: + tone_mid: 'Piquant' + tap_hint: 'Tape la carte ou le bouton pour une autre note' + new_note: 'Nouvelle note' add_quote: 'Couler un 💩' thumb_up: 'Cette 💩 déchire! 👍' thumb_up_label: 'Awesome' diff --git a/assets/locales/sv.yaml b/assets/locales/sv.yaml index 55eb8de..58ad6da 100644 --- a/assets/locales/sv.yaml +++ b/assets/locales/sv.yaml @@ -2,6 +2,9 @@ title: 'Passive Agressive Generator' home: + tone_mid: 'Stark' + tap_hint: 'Tryck på kortet eller knappen för ett nytt citat' + new_note: 'Nytt citat' add_quote: 'Släpp en 💩' thumb_up: 'Den 💩 rockar! 👍' thumb_up_label: 'Awesome' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8793fc2 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,620 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 6b2d487..4f5212d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 0722375..98cf5fd 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 312aef4..65098b0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index e461bca..11291a4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 9216268..74e1fa5 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index da43bc0..a4e12be 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index ecc297b..d36a02b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 312aef4..65098b0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 4f83e4e..140d6a5 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a380d78..7f6b089 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index cd3a2ff..fcad522 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index 723cc8a..2f91f91 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 4915d43..67f5b38 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 1c3732f..167f05d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a380d78..7f6b089 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index bd38590..73054a9 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 5ee5355..496614f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index a6ef266..4dbb52e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index e895ebf..b9e64c1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 9d8eee4..9ba7551 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 80edd8f..ad0f312 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} 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/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 64b60a9..fb028a0 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -1,5 +1,4 @@ -# Based on: -# https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a +# iOS release lanes (not wired to GitHub Actions yet; run locally or add a workflow). # Automatically update fastlane update_fastlane diff --git a/ios/fastlane/flutter_build.sh b/ios/fastlane/flutter_build.sh index 1f2a662..62ff43a 100755 --- a/ios/fastlane/flutter_build.sh +++ b/ios/fastlane/flutter_build.sh @@ -1,7 +1,6 @@ #!/bin/bash -# Based on: -# https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a +# Flutter release build helper for Fastlane (see ios/fastlane/Fastfile). cd ../../ if [ "$1" == "--clean" ] diff --git a/ios/fastlane/flutter_test.sh b/ios/fastlane/flutter_test.sh index f74d5dd..803a9a7 100755 --- a/ios/fastlane/flutter_test.sh +++ b/ios/fastlane/flutter_test.sh @@ -1,7 +1,6 @@ #!/bin/bash -# Based on: -# https://medium.com/appditto/automate-your-flutter-workflow-using-gitlab-ci-cd-and-fastlane-5872e758165a +# Flutter test helper for Fastlane (see ios/fastlane/Fastfile). cd ../../ echo "Running tests" diff --git a/lib/db_helper.dart b/lib/db_helper.dart index 9d5dee4..a785922 100644 --- a/lib/db_helper.dart +++ b/lib/db_helper.dart @@ -38,9 +38,14 @@ class DatabaseHelper { Future _onOpen(Database db) async { AppLogger.debug('Opened database, version ${await db.getVersion()}'); if (!await _isDbValid(db)) { - AppLogger.warning('Invalid db, creating a new one.'); + AppLogger.warning('Invalid db, re-seeding from bundled asset.'); await db.close(); - await _onCreate(db, version); + final data = await rootBundle.load('assets/init_quotes.db'); + final bytes = data.buffer.asUint8List( + data.offsetInBytes, + data.lengthInBytes, + ); + await File(dbPath).writeAsBytes(bytes); } } @@ -65,16 +70,23 @@ class DatabaseHelper { } Future gradeQuote(QuoteModel quote, int increment) async { - quote.grade = (quote.grade ?? 0) + increment; + final updated = quote.copyWith(grade: (quote.grade ?? 0) + increment); + await persistQuoteGrade(updated); + quote.grade = updated.grade; + } + + Future persistQuoteGrade(QuoteModel quote) async { final Database db = await database; - await db.update( + final rows = await db.update( DatabaseHelper.table, - quote.toMap(), + {'grade': quote.grade ?? 0}, where: 'id = ?', whereArgs: [quote.id], - conflictAlgorithm: ConflictAlgorithm.replace, ); - AppLogger.debug('Updated grade of quote ${quote.id} by $increment'); + if (rows == 0) { + throw StateError('No quote updated for id ${quote.id}'); + } + AppLogger.debug('Persisted grade ${quote.grade} for quote ${quote.id}'); } Future>> retrieveAllQuotes() async { diff --git a/lib/main.dart b/lib/main.dart index 65a5793..a5c838e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'states.dart'; import 'screens/splash.dart'; +import 'theme/app_theme.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -35,10 +36,7 @@ class App extends StatelessWidget { return MyStatefulWidget( child: MaterialApp( title: appShortName, - theme: ThemeData( - primarySwatch: appColor, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), + theme: AppTheme.light(), debugShowCheckedModeBanner: false, builder: FlutterI18n.rootAppBuilder(), supportedLocales: [ diff --git a/lib/models/quote.dart b/lib/models/quote.dart index 3426c0a..616c82f 100644 --- a/lib/models/quote.dart +++ b/lib/models/quote.dart @@ -1,4 +1,3 @@ - class QuoteModel { int? id; String? locale; @@ -29,19 +28,47 @@ class QuoteModel { 'theme': theme, 'text': text, 'source': source, - 'grade': grade, + 'grade': grade ?? 0, }; } - QuoteModel.fromMap(Map result) { - this.id = result['id']; - this.locale = result['locale']; - this.origin = result['origin']; - this.theme = result['theme']; - this.level = result['level']; - this.text = result['text']; - this.source = result['source']; - this.grade = result['grade']; + QuoteModel.fromMap(Map result) + : id = _readInt(result['id']), + locale = result['locale'] as String?, + origin = result['origin'] as String?, + theme = result['theme'] as String?, + level = _readInt(result['level']), + text = result['text'] as String?, + source = result['source'] as String?, + grade = _readInt(result['grade']); + + static int? _readInt(dynamic value) { + if (value == null) return null; + if (value is int) return value; + if (value is num) return value.toInt(); + return int.tryParse(value.toString()); + } + + QuoteModel copyWith({ + int? id, + String? locale, + String? origin, + int? level, + String? theme, + String? text, + String? source, + int? grade, + }) { + return QuoteModel( + id: id ?? this.id, + locale: locale ?? this.locale, + origin: origin ?? this.origin, + level: level ?? this.level, + theme: theme ?? this.theme, + text: text ?? this.text, + source: source ?? this.source, + grade: grade ?? this.grade, + ); } @override diff --git a/lib/screens/add_quote.dart b/lib/screens/add_quote.dart index bc8a9b2..91d4c7e 100644 --- a/lib/screens/add_quote.dart +++ b/lib/screens/add_quote.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import '../widgets/appbar.dart'; import '../widgets/add_quote.dart'; +import '../widgets/appbar.dart'; class AddQuoteScreen extends StatelessWidget { const AddQuoteScreen({super.key}); @@ -9,8 +9,8 @@ class AddQuoteScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBarWidget(), + appBar: const AppBarWidget(), body: AddQuoteWidget(), ); } -} \ No newline at end of file +} diff --git a/lib/screens/home.dart b/lib/screens/home.dart index d33a71c..77dd2b3 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -1,51 +1,44 @@ import 'package:flutter/material.dart'; +import '../states.dart'; import '../widgets/appbar.dart'; -import '../widgets/settings.dart'; -import '../widgets/quote.dart'; import '../widgets/drawer.dart'; +import '../widgets/home_actions.dart'; +import '../widgets/quote.dart'; +import '../widgets/settings.dart'; -class HomeScreen extends StatefulWidget { +class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { @override Widget build(BuildContext context) { - switch (MediaQuery.of(context).orientation) { - case Orientation.landscape: - return _landscapeHome(context); - case Orientation.portrait: - return _portraitHome(context); - } - } + final app = MyStatefulWidget.watch(context); - Scaffold _portraitHome(BuildContext context) { return Scaffold( appBar: const AppBarWidget(), drawer: const DrawerWidget(), - body: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - SettingsWidget(), - QuoteWidget(child: QuoteText()), - ], - ), - ); - } - - Scaffold _landscapeHome(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - QuoteWidget(child: QuoteText()), - ], + body: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SettingsWidget(), + const SizedBox(height: 12), + Expanded( + child: QuoteWidget( + child: QuoteText( + key: ValueKey(app.quoteRevision), + quote: app.quote, + onPickQuote: app.state.pickQuote, + ), + ), + ), + const SizedBox(height: 12), + HomeActions(key: ValueKey(app.quoteRevision)), + ], + ), + ), ), ); } diff --git a/lib/screens/onboarding.dart b/lib/screens/onboarding.dart index a7cd415..cda6602 100644 --- a/lib/screens/onboarding.dart +++ b/lib/screens/onboarding.dart @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; import 'package:introduction_screen/introduction_screen.dart'; import 'home.dart'; -import '../states.dart'; - +import '../theme/app_theme.dart'; class OnboardScreen extends StatefulWidget { const OnboardScreen({super.key}); @@ -17,7 +16,7 @@ class _OnboardScreenState extends State { void _onIntroEnd(BuildContext context) { Navigator.of(context).push( - MaterialPageRoute(builder: (_) => const HomeScreen()), + MaterialPageRoute(builder: (_) => const HomeScreen()), ); } @@ -28,13 +27,13 @@ class _OnboardScreenState extends State { titleTextStyle: TextStyle(fontSize: 25.0, fontWeight: FontWeight.w700), bodyTextStyle: bodyStyle, //descriptionPadding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0), - pageColor: postItColor, + pageColor: AppColors.notePaper, imagePadding: EdgeInsets.zero, ); return IntroductionScreen( key: introKey, - globalBackgroundColor: appColor, + globalBackgroundColor: AppColors.cream, showSkipButton: true, showNextButton: true, next: const Icon(Icons.arrow_forward), @@ -42,48 +41,56 @@ class _OnboardScreenState extends State { done: const Text('Shoot!', style: TextStyle(fontWeight: FontWeight.w600)), onDone: () => _onIntroEnd(context), onSkip: () => _onIntroEnd(context), - pages: [ - PageViewModel( - title: 'You filthy thing! 💩', - body: - 'You are bad... real bad...\nbad enough to use this insult generator!', - image: Center(child: Image.asset('assets/images/trollface.png', height: 175.0)), - decoration: pageDecoration, + pages: [ + PageViewModel( + title: 'You filthy thing! 💩', + body: + 'You are bad... real bad...\nbad enough to use this insult generator!', + image: Center( + child: Image.asset('assets/images/trollface.png', height: 175.0)), + decoration: pageDecoration, ), - PageViewModel( - title: 'Select how agressive you feel 🤬', - body: - 'Use the slider! \n(yes like the one above) 😂', - image: Center(child: Image.asset('assets/images/onboard_slider.png', height: 175.0)), - decoration: pageDecoration, + PageViewModel( + title: 'Select how agressive you feel 🤬', + body: 'Use the slider! \n(yes like the one above) 😂', + image: Center( + child: Image.asset('assets/images/onboard_slider.png', + height: 175.0)), + decoration: pageDecoration, ), - PageViewModel( - title: 'A specific frustration in mind? 😠', - body: 'Select it from here with your 🖕!', - image: Center(child: Image.asset('assets/images/onboard_theme.png', height: 175.0)), - decoration: pageDecoration, + PageViewModel( + title: 'A specific frustration in mind? 😠', + body: 'Select it from here with your 🖕!', + image: Center( + child: Image.asset('assets/images/onboard_theme.png', + height: 175.0)), + decoration: pageDecoration, ), - PageViewModel( - title: 'Tap here and get tons of 💩!', - body: - 'Bang, here you go!\nTap it baby! ', - image: Center(child: Image.asset('assets/images/onboard_note.png', height: 175.0)), - decoration: pageDecoration, + PageViewModel( + title: 'Tap here and get tons of 💩!', + body: 'Bang, here you go!\nTap it baby! ', + image: Center( + child: + Image.asset('assets/images/onboard_note.png', height: 175.0)), + decoration: pageDecoration, ), - PageViewModel( - title: 'If you are real bad...🤦', - body: - 'Drop your own 💩 or rate them by tapping there! 🖖', - image: Center(child: Image.asset('assets/images/onboard_menu.png', height: 175.0)), - decoration: pageDecoration, + PageViewModel( + title: 'If you are real bad...🤦', + body: 'Drop your own 💩 or rate them by tapping there! 🖖', + image: Center( + child: + Image.asset('assets/images/onboard_menu.png', height: 175.0)), + decoration: pageDecoration, ), - PageViewModel( - title: 'So long suckers! 🚀', - body: ''' Don't forget to give (constructive) feedback + PageViewModel( + title: 'So long suckers! 🚀', + body: ''' Don't forget to give (constructive) feedback \n\n\n (Or buy the useless author a cup of tea 🍵) (https://www.buymeacoffee.com/kodsama)''', - image: Center(child: Image.asset('assets/images/coffee_mug.png', height: 175.0)), - decoration: pageDecoration, + image: Center( + child: + Image.asset('assets/images/coffee_mug.png', height: 175.0)), + decoration: pageDecoration, ), ], ); diff --git a/lib/screens/splash.dart b/lib/screens/splash.dart index 705c378..918f0a6 100644 --- a/lib/screens/splash.dart +++ b/lib/screens/splash.dart @@ -6,6 +6,7 @@ import 'package:flutter_i18n/flutter_i18n.dart'; import 'onboarding.dart'; import 'home.dart'; import '../states.dart'; +import '../theme/app_theme.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @@ -44,10 +45,7 @@ class _SplashScreenState extends State duration: const Duration(milliseconds: 1000), vsync: this, ); - animation = CurvedAnimation( - parent: controller, - curve: Curves.easeIn - ); + animation = CurvedAnimation(parent: controller, curve: Curves.easeIn); controller.forward(); } @@ -66,18 +64,18 @@ class _SplashScreenState extends State if (seen) { Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => const HomeScreen())); + MaterialPageRoute(builder: (context) => const HomeScreen())); } else { prefs.setBool('seen', true); Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => const OnboardScreen())); + MaterialPageRoute(builder: (context) => const OnboardScreen())); } } @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: appColor, + backgroundColor: AppColors.lemon, body: FadeTransition( opacity: animation, child: Center( diff --git a/lib/states.dart b/lib/states.dart index 2a70688..b9f1cf8 100644 --- a/lib/states.dart +++ b/lib/states.dart @@ -1,29 +1,34 @@ +import 'dart:async'; import 'dart:math'; + import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'models/quote.dart'; import 'db_helper.dart'; +import 'theme/app_theme.dart'; import 'utils/app_logger.dart'; -const Map color = { - 50:Color.fromRGBO(4,131,184, .1), - 100:Color.fromRGBO(4,131,184, .2), - 200:Color.fromRGBO(4,131,184, .3), - 300:Color.fromRGBO(4,131,184, .4), - 400:Color.fromRGBO(4,131,184, .5), - 500:Color.fromRGBO(4,131,184, .6), - 600:Color.fromRGBO(4,131,184, .7), - 700:Color.fromRGBO(4,131,184, .8), - 800:Color.fromRGBO(4,131,184, .9), - 900:Color.fromRGBO(4,131,184, 1), -}; -const MaterialColor appColor = MaterialColor(0xFFEFF294, color); -const MaterialColor postItColor = MaterialColor(0xFFF2EFBD, color); - -final String appShortName = 'PAGen'; -final String appLongName = 'Passive Agressive Generator'; +// Legacy swatch used by splash until fully migrated. +const MaterialColor appColor = MaterialColor(0xFFFFE566, { + 50: AppColors.lemon, + 100: AppColors.lemon, + 200: AppColors.lemon, + 300: AppColors.lemon, + 400: AppColors.lemon, + 500: AppColors.lemon, + 600: AppColors.lemon, + 700: AppColors.lemon, + 800: AppColors.lemon, + 900: AppColors.lemon, +}); + +/// Post-it background for the quote area (legacy name kept for quote widget). +const Color postItColor = AppColors.postIt; + +const String appShortName = 'PAGen'; +const String appLongName = 'Passive Agressive Generator'; class MyStatefulWidget extends StatefulWidget { final Widget child; @@ -33,7 +38,12 @@ class MyStatefulWidget extends StatefulWidget { static MyStatefulWidgetState of(BuildContext context) { return context .dependOnInheritedWidgetOfExactType()! - .data; + .state; + } + + /// Subscribe to quote / grade changes (use in screen build methods). + static MyInheritedWidget watch(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType()!; } @override @@ -43,107 +53,177 @@ class MyStatefulWidget extends StatefulWidget { class MyStatefulWidgetState extends State { final List _quotes = []; QuoteModel? _quote; - double _paScale = 1; + int _paLevel = 1; String _paTheme = 'Random'; + int _quoteRevision = 0; + final List _paThemes = [ - "Random", - "Public", - "Laundry", - "Kitchen", - "Custom" - ]; + 'Random', + 'Public', + 'Laundry', + 'Kitchen', + 'Custom', + ]; + final List _paScales = [ - "Passive", - "Passive-agressive", - "Agressive", + 'Passive', + 'Passive-agressive', + 'Agressive', ]; QuoteModel? get quote => _quote; - double get paScale => _paScale; + int get quoteRevision => _quoteRevision; + int get paLevel => _paLevel; + double get paScale => _paLevel.toDouble(); String get paTheme => _paTheme; List get paThemes => _paThemes; List get paScales => _paScales; + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _bootstrap()); + } + + Future _bootstrap() async { + await refreshQuotes(); + if (_quote == null && mounted) { + await pickQuote(); + } + } + + void updatePaLevel(int level) { + if (_paLevel == level) return; + setState(() => _paLevel = level); + pickQuote(); + } + void updatePaScale(double value) { - _paScale = value; + updatePaLevel(value.round()); } + void updatePaTheme(String theme) { - _paTheme = theme; + if (_paTheme == theme) return; + setState(() => _paTheme = theme); + pickQuote(); } Future refreshQuotes() async { final results = await DatabaseHelper.instance.retrieveAllQuotes(); - for (int i = 0; i < results.length; i++) { - _quotes.add(QuoteModel.fromMap(results[i])); - } + if (!mounted) return; + setState(() { + _quotes + ..clear() + ..addAll(results.map(QuoteModel.fromMap)); + }); } - void pickQuote() async { + Future pickQuote() async { if (_quotes.isEmpty) { await refreshQuotes(); } - // Get sublist of quotes based on level and then topic with length checks - List byTheme = []; - List byLvl = []; - byLvl = _quotes.where((e) => e.level == _paScale).toList(); - if (byLvl.isEmpty) { - // Not enough quotes, use full list - byTheme = _quotes; - } else { - if (_paTheme == 'Random') { - byTheme = byLvl; // Doesn't matter which theme we use - } else { - byTheme = byLvl.where((e) => e.theme == _paTheme).toList(); - } - if (byTheme.isEmpty) byTheme = byLvl; // Not enough, use level + if (_quotes.isEmpty || !mounted) return; + + List pool = _quotes.where((e) => e.level == _paLevel).toList(); + if (pool.isEmpty) pool = List.from(_quotes); + + if (_paTheme != 'Random') { + final themed = pool.where((e) => e.theme == _paTheme).toList(); + if (themed.isNotEmpty) pool = themed; } - if (byTheme.isEmpty) return; + + _publishQuote(pool[Random().nextInt(pool.length)]); + } + + void _publishQuote(QuoteModel updated) { setState(() { - _quote = byTheme[Random().nextInt(byTheme.length)]; + _quote = updated; + _quoteRevision++; + final index = _quotes.indexWhere((q) => q.id == updated.id); + if (index >= 0) { + _quotes[index] = updated; + } }); } - void incrementQuoteGrade(QuoteModel? quote, int increment) { - String toast; - if (quote != null) { - DatabaseHelper.instance.gradeQuote(quote, increment); - toast = FlutterI18n.translate(context, 'states.success'); - } else { - AppLogger.warning('Attempted to grade a null quote.'); - toast = FlutterI18n.translate(context, 'states.fail'); + /// Bump the visible note's score (+1 / -1) and refresh the card immediately. + void bumpQuoteGrade(int increment) { + final current = _quote; + if (current?.id == null) { + AppLogger.warning('Attempted to grade without a loaded quote.'); + _showToast(FlutterI18n.translate(context, 'states.fail')); + return; } + + final newGrade = (current!.grade ?? 0) + increment; + final updated = current.copyWith(grade: newGrade); + _publishQuote(updated); + _showToast(FlutterI18n.translate(context, 'states.success')); + + unawaited(_persistGradeQuietly(updated)); + } + + Future _persistGradeQuietly(QuoteModel quote) async { + try { + await DatabaseHelper.instance.persistQuoteGrade(quote); + } catch (e, st) { + AppLogger.warning( + 'Failed to persist quote grade (UI already updated)', + error: e, + stackTrace: st, + ); + } + } + + void _showToast(String msg) { Fluttertoast.showToast( - msg: toast, - toastLength: Toast.LENGTH_LONG, + msg: msg, + toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, - timeInSecForIosWeb: 1, - backgroundColor: Colors.grey, + backgroundColor: AppColors.ink, textColor: Colors.white, - fontSize: 15.0 + fontSize: 14, ); } @override Widget build(BuildContext context) { - // pickQuote(); // Comment out to load a random quote at startup return MyInheritedWidget( + quote: _quote, + quoteRevision: _quoteRevision, + paLevel: _paLevel, + paTheme: _paTheme, + state: this, child: widget.child, - data: this, ); } } class MyInheritedWidget extends InheritedWidget { - final MyStatefulWidgetState data; + final QuoteModel? quote; + final int quoteRevision; + final int paLevel; + final String paTheme; + final MyStatefulWidgetState state; const MyInheritedWidget({ super.key, + required this.quote, + required this.quoteRevision, + required this.paLevel, + required this.paTheme, + required this.state, required super.child, - required this.data, }); + int get displayGrade => quote?.grade ?? 0; + @override - bool updateShouldNotify(InheritedWidget oldWidget) { - return true; + bool updateShouldNotify(MyInheritedWidget oldWidget) { + return quoteRevision != oldWidget.quoteRevision || + quote != oldWidget.quote || + displayGrade != oldWidget.displayGrade || + paLevel != oldWidget.paLevel || + paTheme != oldWidget.paTheme; } -} \ No newline at end of file +} diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..453d939 --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +/// Playful palette inspired by friendly learning apps (warm, high contrast, fun). +abstract final class AppColors { + static const cream = Color(0xFFFFF8F0); + static const lemon = Color(0xFFFFE566); + static const coral = Color(0xFFFF6B4A); + static const ink = Color(0xFF2D3142); + static const inkMuted = Color(0xFF6B7280); + static const notePaper = Color(0xFFFFFDF5); + static const postIt = Color(0xFFF2EFBD); + static const mint = Color(0xFF58C9B9); + static const lilac = Color(0xFFB8B5FF); +} + +abstract final class AppTheme { + static ThemeData light() { + final base = ThemeData( + useMaterial3: true, + brightness: Brightness.light, + colorScheme: ColorScheme.fromSeed( + seedColor: AppColors.coral, + primary: AppColors.coral, + secondary: AppColors.mint, + surface: AppColors.cream, + onSurface: AppColors.ink, + ), + scaffoldBackgroundColor: AppColors.cream, + cardTheme: CardThemeData( + color: AppColors.notePaper, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + ), + appBarTheme: const AppBarTheme( + backgroundColor: AppColors.cream, + foregroundColor: AppColors.ink, + elevation: 0, + centerTitle: true, + ), + drawerTheme: const DrawerThemeData( + backgroundColor: AppColors.notePaper, + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: AppColors.coral, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + textStyle: GoogleFonts.nunito( + fontWeight: FontWeight.w800, + fontSize: 16, + ), + ), + ), + chipTheme: ChipThemeData( + backgroundColor: Colors.white, + selectedColor: AppColors.lemon, + labelStyle: GoogleFonts.nunito( + fontWeight: FontWeight.w700, + color: AppColors.ink, + ), + side: const BorderSide(color: Color(0xFFE8E4DC)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + ), + ); + + return base.copyWith( + textTheme: GoogleFonts.nunitoTextTheme(base.textTheme).apply( + bodyColor: AppColors.ink, + displayColor: AppColors.ink, + ), + ); + } +} diff --git a/lib/widgets/add_quote.dart b/lib/widgets/add_quote.dart index 19e0a57..9629757 100644 --- a/lib/widgets/add_quote.dart +++ b/lib/widgets/add_quote.dart @@ -36,10 +36,13 @@ class _AddQuoteState extends State { return SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( - children: [ + children: [ SizedBox(height: 10), - Text(FlutterI18n.translate(context, 'add_quote.add'), - style: TextStyle(fontSize: 20.0,), + Text( + FlutterI18n.translate(context, 'add_quote.add'), + style: TextStyle( + fontSize: 20.0, + ), ), SizedBox(height: 30), TextField( @@ -66,48 +69,54 @@ class _AddQuoteState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: [ + children: [ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(FlutterI18n.translate(context, 'add_quote.pa_scale'), - style: TextStyle(fontSize: 12.0,), - ), - DropdownButton( - value: paScales[paScale], - items: paScales - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - // icon: Icon(Icons.arrow_downward), - iconSize: 24, - elevation: 16, - style: TextStyle( - color: Colors.orange - ), - underline: Container(), - onChanged: (String? value) { - if (value != null) paScale = paScales.indexOf(value); - }, + children: [ + Text( + FlutterI18n.translate(context, 'add_quote.pa_scale'), + style: TextStyle( + fontSize: 12.0, ), + ), + DropdownButton( + value: paScales[paScale], + items: + paScales.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + // icon: Icon(Icons.arrow_downward), + iconSize: 24, + elevation: 16, + style: TextStyle(color: Colors.orange), + underline: Container(), + onChanged: (String? value) { + if (value != null) { + setState(() => paScale = paScales.indexOf(value)); + } + }, + ), ], ), SizedBox(width: 40), Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(FlutterI18n.translate(context, 'add_quote.theme'), - style: TextStyle(fontSize: 12.0,), + children: [ + Text( + FlutterI18n.translate(context, 'add_quote.theme'), + style: TextStyle( + fontSize: 12.0, + ), ), DropdownButton( value: paTheme, - items: paThemes - .map>((String value) { + items: + paThemes.map>((String value) { return DropdownMenuItem( value: value, child: Text(value), @@ -116,17 +125,15 @@ class _AddQuoteState extends State { // icon: Icon(Icons.arrow_downward), iconSize: 24, elevation: 16, - style: TextStyle( - color: Colors.orange - ), + style: TextStyle(color: Colors.orange), underline: Container(), onChanged: (String? value) { - if (value != null) paTheme = value; + if (value != null) setState(() => paTheme = value); }, ), ], ), - ], + ], ), const SizedBox(height: 20), TextButton( @@ -169,4 +176,4 @@ class _AddQuoteState extends State { MyStatefulWidget.of(context).refreshQuotes(); Navigator.pop(context, FlutterI18n.translate(context, 'add_quote.saved')); } -} \ No newline at end of file +} diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index 1f3bddb..a554369 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -1,40 +1,41 @@ import 'package:flutter/material.dart'; -import 'package:auto_size_text/auto_size_text.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../states.dart'; +import '../theme/app_theme.dart'; class AppBarWidget extends StatelessWidget implements PreferredSizeWidget { const AppBarWidget({super.key}); - final String title = 'Passive Agressive Generator'; - @override - Size get preferredSize => const Size.fromHeight(56); + Size get preferredSize => const Size.fromHeight(kToolbarHeight); @override Widget build(BuildContext context) { return AppBar( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( + title: Row( + children: [ + Image.asset( 'assets/images/trollface.png', fit: BoxFit.contain, - height: 60, - ), - Expanded( - child: AutoSizeText( - title, - style: TextStyle( - fontSize: 20, - color: Colors.black, - ), - minFontSize: 12, - maxLines: 1, - overflow: TextOverflow.ellipsis, + height: 36, + width: 36, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLongName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.nunito( + fontWeight: FontWeight.w900, + fontSize: 17, + color: AppColors.ink, ), ), - ], - ), - ); + ), + ], + ), + ); } } diff --git a/lib/widgets/drawer.dart b/lib/widgets/drawer.dart index a3e533c..a8e7a2a 100644 --- a/lib/widgets/drawer.dart +++ b/lib/widgets/drawer.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:url_launcher/url_launcher.dart'; -import '../states.dart'; -import '../screens/onboarding.dart'; import '../screens/add_quote.dart'; -import '../models/quote.dart'; +import '../screens/onboarding.dart'; +import '../states.dart'; +import '../theme/app_theme.dart'; import '../utils/app_logger.dart'; class DrawerWidget extends StatefulWidget { @@ -16,73 +17,87 @@ class DrawerWidget extends StatefulWidget { } class _DrawerWidgetState extends State { - QuoteModel? quote; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - MyStatefulWidgetState data = MyStatefulWidget.of(context); - quote = data.quote; - } - @override Widget build(BuildContext context) { return Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - Container( - height: 80.0, - child: DrawerHeader( - child: Text(FlutterI18n.translate(context, 'home.god_mode')), - decoration: BoxDecoration( - color: appColor, + child: SafeArea( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 8), + children: [ + DrawerHeader( + margin: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), + decoration: const BoxDecoration(color: AppColors.lemon), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + 'assets/images/trollface.png', + height: 48, + width: 48, + ), + const SizedBox(height: 12), + Text( + FlutterI18n.translate(context, 'home.god_mode'), + style: GoogleFonts.nunito( + fontWeight: FontWeight.w900, + fontSize: 18, + color: AppColors.ink, + ), + ), + ], ), ), - ), - ListTile( - title: Text(FlutterI18n.translate(context, 'home.view_demo')), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (_) => OnboardScreen()), - ); - }, - ), - ListTile( - title: Text(FlutterI18n.translate(context, 'home.add_quote')), - onTap: () { - Navigator.pop(context); - Navigator.of(context).push( - MaterialPageRoute(builder: (_) => AddQuoteScreen()), - ); - }, - ), - ListTile( - title: Text(FlutterI18n.translate(context, 'home.thumb_up')), - onTap: () { - MyStatefulWidget.of(context).incrementQuoteGrade(quote, 1); - Navigator.of(context).pop(); - }, - ), - ListTile( - title: Text(FlutterI18n.translate(context, 'home.thumb_down')), - onTap: () { - MyStatefulWidget.of(context).incrementQuoteGrade(quote, -1); - Navigator.of(context).pop(); - }, - ), - ListTile( - title: Text(FlutterI18n.translate(context, 'home.buy_tea')), - onTap: () { - // Navigator.pop(context); - _launchBuyMeCoffeeURL(); - }, - ), - ], + _DrawerTile( + icon: Icons.school_outlined, + label: FlutterI18n.translate(context, 'home.view_demo'), + onTap: () { + Navigator.pop(context); + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => OnboardScreen()), + ); + }, + ), + _DrawerTile( + icon: Icons.edit_note_outlined, + label: FlutterI18n.translate(context, 'home.add_quote'), + onTap: () { + Navigator.pop(context); + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => AddQuoteScreen()), + ); + }, + ), + _DrawerTile( + icon: Icons.thumb_up_outlined, + label: FlutterI18n.translate(context, 'home.thumb_up'), + onTap: () { + _gradeAndClose(context, 1); + }, + ), + _DrawerTile( + icon: Icons.thumb_down_outlined, + label: FlutterI18n.translate(context, 'home.thumb_down'), + onTap: () { + _gradeAndClose(context, -1); + }, + ), + _DrawerTile( + icon: Icons.local_cafe_outlined, + label: FlutterI18n.translate(context, 'home.buy_tea'), + onTap: _launchBuyMeCoffeeURL, + ), + ], + ), ), ); } + void _gradeAndClose(BuildContext context, int delta) { + MyStatefulWidget.of(context).bumpQuoteGrade(delta); + Navigator.pop(context); + } + Future _launchBuyMeCoffeeURL() async { final Uri url = Uri.parse('https://www.buymeacoffee.com/kodsama'); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { @@ -90,3 +105,28 @@ class _DrawerWidgetState extends State { } } } + +class _DrawerTile extends StatelessWidget { + const _DrawerTile({ + required this.icon, + required this.label, + required this.onTap, + }); + + final IconData icon; + final String label; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(icon, color: AppColors.coral), + title: Text( + label, + style: GoogleFonts.nunito(fontWeight: FontWeight.w700), + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + onTap: onTap, + ); + } +} diff --git a/lib/widgets/home_actions.dart b/lib/widgets/home_actions.dart new file mode 100644 index 0000000..d207b16 --- /dev/null +++ b/lib/widgets/home_actions.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; + +import '../states.dart'; +import '../theme/app_theme.dart'; + +class HomeActions extends StatelessWidget { + const HomeActions({super.key}); + + @override + Widget build(BuildContext context) { + final app = MyStatefulWidget.watch(context); + final quote = app.quote; + final hasQuote = quote?.id != null && (quote?.text?.isNotEmpty ?? false); + + return Row( + children: [ + Expanded( + child: _ActionButton( + icon: Icons.thumb_up_rounded, + label: FlutterI18n.translate(context, 'home.thumb_up_label'), + color: AppColors.mint, + enabled: hasQuote, + onPressed: hasQuote ? () => app.state.bumpQuoteGrade(1) : null, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _ActionButton( + icon: Icons.thumb_down_rounded, + label: FlutterI18n.translate(context, 'home.thumb_down_label'), + color: AppColors.lilac, + enabled: hasQuote, + onPressed: hasQuote ? () => app.state.bumpQuoteGrade(-1) : null, + ), + ), + ], + ); + } +} + +class _ActionButton extends StatelessWidget { + const _ActionButton({ + required this.icon, + required this.label, + required this.color, + required this.enabled, + required this.onPressed, + }); + + final IconData icon; + final String label; + final Color color; + final bool enabled; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return Material( + color: enabled ? color.withValues(alpha: 0.25) : Colors.white, + borderRadius: BorderRadius.circular(20), + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: onPressed, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + color: enabled ? AppColors.ink : AppColors.inkMuted, + size: 22, + ), + const SizedBox(width: 6), + Flexible( + child: Text( + label, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w800, + color: enabled ? AppColors.ink : AppColors.inkMuted, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/quote.dart b/lib/widgets/quote.dart index 4d08de1..1079a52 100644 --- a/lib/widgets/quote.dart +++ b/lib/widgets/quote.dart @@ -1,112 +1,180 @@ import 'package:flutter/material.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:google_fonts/google_fonts.dart'; -import '../states.dart'; import '../models/quote.dart'; +import '../theme/app_theme.dart'; +/// Post-it note area; hosts [QuoteText] (or another child) and a primary action. class QuoteWidget extends StatelessWidget { final Widget child; const QuoteWidget({super.key, required this.child}); - void onPressed(BuildContext context) { - MyStatefulWidget.of(context).pickQuote(); - } - @override Widget build(BuildContext context) { - return Expanded( - child: Container( - color: postItColor, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: postItColor, - foregroundColor: Colors.black, - elevation: 0, - shape: const RoundedRectangleBorder(), - ), - onPressed: () { - onPressed(context); - }, - child: child, - ), + return Material( + color: AppColors.postIt, + elevation: 4, + shadowColor: AppColors.coral.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(24), + clipBehavior: Clip.antiAlias, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: child, ), ); } } -class QuoteText extends StatefulWidget { - const QuoteText({super.key}); +class QuoteText extends StatelessWidget { + const QuoteText({ + super.key, + required this.quote, + required this.onPickQuote, + }); + + final QuoteModel? quote; + final Future Function() onPickQuote; @override - State createState() => QuoteTextState(); -} + Widget build(BuildContext context) { + final welcome = FlutterI18n.translate(context, 'states.welcome'); + final hint = FlutterI18n.translate(context, 'home.tap_hint'); -class QuoteTextState extends State { - QuoteModel? quote; - double fontSize = 20.0; + final hasRealQuote = + quote?.id != null && (quote?.text?.isNotEmpty ?? false); + final displayGrade = quote?.grade ?? 0; - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final MyStatefulWidgetState data = MyStatefulWidget.of(context); - quote = data.quote; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onPickQuote, + child: Center( + child: hasRealQuote + ? _QuoteContent( + quote: quote!, + displayGrade: displayGrade, + ) + : _WelcomeText(text: welcome), + ), + ), + ), + FilledButton.icon( + onPressed: onPickQuote, + icon: Image.asset( + 'assets/images/trollface.png', + height: 26, + width: 26, + ), + label: Text(FlutterI18n.translate(context, 'home.new_note')), + ), + const SizedBox(height: 8), + Text( + hint, + textAlign: TextAlign.center, + style: GoogleFonts.nunito( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.inkMuted, + ), + ), + ], + ); } +} + +class _QuoteContent extends StatelessWidget { + const _QuoteContent({ + required this.quote, + required this.displayGrade, + }); + + final QuoteModel quote; + final int displayGrade; @override Widget build(BuildContext context) { - final QuoteModel currentQuote = quote ?? - QuoteModel( - text: FlutterI18n.translate(context, 'states.welcome'), - source: '', - ); + final source = (quote.source ?? '').trim(); + return Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: [ AutoSizeText( - '${currentQuote.text}', - style: TextStyle(fontSize: fontSize, color: Colors.black), - minFontSize: 12, - maxLines: 5, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 60), // Spacing - Align( - alignment: Alignment.centerRight, - child: AutoSizeText( - '- ${currentQuote.source}', - style: TextStyle(fontSize: fontSize - 5, color: Colors.grey), - minFontSize: 12, - maxLines: 1, - overflow: TextOverflow.ellipsis, + quote.text ?? '', + textAlign: TextAlign.center, + style: GoogleFonts.nunito( + fontSize: 22, + fontWeight: FontWeight.w800, + height: 1.35, + color: AppColors.ink, ), + minFontSize: 14, + maxLines: 8, ), - const SizedBox(height: 10), // Spacing + if (source.isNotEmpty) ...[ + const SizedBox(height: 14), + Align( + alignment: Alignment.centerRight, + child: AutoSizeText( + '— $source', + style: GoogleFonts.nunito( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.inkMuted, + fontStyle: FontStyle.italic, + ), + minFontSize: 11, + maxLines: 2, + ), + ), + ], + const SizedBox(height: 10), Align( alignment: Alignment.centerRight, - child: AutoSizeText( - _getGradeText(currentQuote.grade), - style: TextStyle(fontSize: fontSize, color: Colors.grey), - minFontSize: 12, - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: Text( + _gradeLabel(displayGrade), + key: ValueKey(displayGrade), + style: GoogleFonts.nunito( + fontSize: 16, + fontWeight: FontWeight.w800, + color: AppColors.ink, + ), ), ), ], ); } - String _getGradeText(int? grade) { - if (grade == null) { - return ''; - } - if (grade > 0) { - return '👍 $grade'; - } - if (grade < 0) { - return '👎 $grade'; - } + String _gradeLabel(int grade) { + if (grade > 0) return '👍 $grade'; + if (grade < 0) return '👎 $grade'; return '🤷 $grade'; } -} \ No newline at end of file +} + +class _WelcomeText extends StatelessWidget { + const _WelcomeText({required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return AutoSizeText( + text, + textAlign: TextAlign.center, + style: GoogleFonts.nunito( + fontSize: 20, + fontWeight: FontWeight.w700, + color: AppColors.inkMuted, + height: 1.4, + ), + minFontSize: 14, + maxLines: 4, + ); + } +} diff --git a/lib/widgets/settings.dart b/lib/widgets/settings.dart index e504506..1ab67f3 100644 --- a/lib/widgets/settings.dart +++ b/lib/widgets/settings.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../states.dart'; +import '../theme/app_theme.dart'; import 'slider.dart'; class SettingsWidget extends StatefulWidget { @@ -12,161 +14,133 @@ class SettingsWidget extends StatefulWidget { } class _SettingsState extends State { + static const int _paScaleLevels = 2; + late String paTheme; late double paScale; - final int _paScaleLevels = 2; - final List _paThemes = [ - "Random", - "Public", - "Laundry", - "Kitchen" - ]; - @override void didChangeDependencies() { super.didChangeDependencies(); - MyStatefulWidgetState data = MyStatefulWidget.of(context); + _syncFromAppState(); + } + + void _syncFromAppState() { + final data = MyStatefulWidget.of(context); paScale = data.paScale; paTheme = data.paTheme; } - void sliderChange (double value) { - setState(() { - paScale = value; - MyStatefulWidget.of(context).updatePaScale(value); - }); + void _sliderChange(double value) { + setState(() => paScale = value); + MyStatefulWidget.of(context).updatePaScale(value); } - void themeChange (String value) { - setState(() { - paTheme = value; - MyStatefulWidget.of(context).updatePaTheme(value); - }); + void _themeChange(String value) { + setState(() => paTheme = value); + MyStatefulWidget.of(context).updatePaTheme(value); } @override Widget build(BuildContext context) { - return Container( - height: 0.2 * (MediaQuery.of(context).size.height), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(height: 20), - Text( - FlutterI18n.translate(context, 'settings.pa'), - ), - SizedBox(height: 10), - _sliderCreate(), - _sourceCreate(), - ], - ), - ); - } + final themes = MyStatefulWidget.of(context).paThemes; - Row _sourceCreate() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: MediaQuery.of(context).size.width * 0.45, - child: Text(FlutterI18n.translate(context, 'settings.theme')), - ), - DropdownButton( - value: paTheme, - items: _paThemes.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - iconSize: 24, - elevation: 16, - style: const TextStyle(color: Colors.orange), - underline: Container(), - onChanged: (String? value) { - if (value != null) themeChange(value); - }, + return Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 14, 16, 14), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + FlutterI18n.translate(context, 'settings.pa'), + style: GoogleFonts.nunito( + fontWeight: FontWeight.w800, + fontSize: 14, + color: AppColors.inkMuted, + ), + ), + const SizedBox(height: 10), + _buildAggressionSlider(context), + const SizedBox(height: 14), + Text( + FlutterI18n.translate(context, 'settings.theme'), + style: GoogleFonts.nunito( + fontWeight: FontWeight.w800, + fontSize: 14, + color: AppColors.inkMuted, + ), + ), + const SizedBox(height: 8), + _buildThemeDropdown(context, themes), + ], ), - ], + ), ); } - Container _sliderCreate() { - double paddingFactor = .2; - double sliderHeight = 48; + Widget _buildAggressionSlider(BuildContext context) { + const sliderHeight = 48.0; + const paddingFactor = 0.2; return Container( - width: MediaQuery.of(context).size.width, height: sliderHeight, decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(sliderHeight * .3), - ), + borderRadius: BorderRadius.circular(sliderHeight * 0.3), gradient: const LinearGradient( - colors: [ - Colors.orange, - Colors.red, - ], - begin: FractionalOffset(0.0, 0.0), - end: FractionalOffset(1.0, 1.00), - stops: [0.0, 1.0], - tileMode: TileMode.clamp), + colors: [AppColors.mint, AppColors.coral], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), ), child: Padding( - padding: EdgeInsets.fromLTRB(sliderHeight * paddingFactor, - 2, sliderHeight * paddingFactor, 2), + padding: EdgeInsets.fromLTRB( + sliderHeight * paddingFactor, + 2, + sliderHeight * paddingFactor, + 2, + ), child: Row( - children: [ + children: [ Text( FlutterI18n.translate(context, 'settings.p'), - textAlign: TextAlign.center, style: TextStyle( - fontSize: sliderHeight * .2, - fontWeight: FontWeight.w700, + fontSize: sliderHeight * 0.2, + fontWeight: FontWeight.w800, color: Colors.white, - ), ), - SizedBox( - width: sliderHeight * .1, - ), + SizedBox(width: sliderHeight * 0.1), Expanded( - child: Center( - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - activeTrackColor: Colors.white.withValues(alpha: 1), - inactiveTrackColor: Colors.white.withValues(alpha: .5), - trackHeight: 4.0, - thumbShape: CustomSliderThumbCircle( - thumbRadius: sliderHeight * .4, - min: 0, - max: _paScaleLevels, - writeValue: false, - ), - overlayColor: Colors.white.withValues(alpha: .4), - activeTickMarkColor: Colors.white, - inactiveTickMarkColor: Colors.red.withValues(alpha: .7), - ), - child: Slider( - max: _paScaleLevels.toDouble(), - divisions: _paScaleLevels, - // label: 'Level', - value: paScale, - onChanged: (double value) { sliderChange(value); }, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: Colors.white, + inactiveTrackColor: Colors.white.withValues(alpha: 0.45), + trackHeight: 4, + thumbShape: CustomSliderThumbCircle( + thumbRadius: sliderHeight * 0.4, + min: 0, + max: _paScaleLevels, + writeValue: false, ), + overlayColor: Colors.white.withValues(alpha: 0.35), + ), + child: Slider( + min: 0, + max: _paScaleLevels.toDouble(), + divisions: _paScaleLevels, + value: paScale, + onChanged: _sliderChange, ), ), ), - SizedBox( - width: sliderHeight * .1, - ), + SizedBox(width: sliderHeight * 0.1), Text( FlutterI18n.translate(context, 'settings.a'), - textAlign: TextAlign.center, style: TextStyle( - fontSize: sliderHeight * .2, - fontWeight: FontWeight.w700, + fontSize: sliderHeight * 0.2, + fontWeight: FontWeight.w800, color: Colors.white, ), ), @@ -175,4 +149,37 @@ class _SettingsState extends State { ), ); } + + Widget _buildThemeDropdown(BuildContext context, List themes) { + return DecoratedBox( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: const Color(0xFFE8E4DC)), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: themes.contains(paTheme) ? paTheme : themes.first, + items: themes + .map( + (theme) => DropdownMenuItem( + value: theme, + child: Text( + theme, + style: GoogleFonts.nunito(fontWeight: FontWeight.w700), + ), + ), + ) + .toList(), + onChanged: (value) { + if (value != null) _themeChange(value); + }, + ), + ), + ), + ); + } } diff --git a/lib/widgets/slider.dart b/lib/widgets/slider.dart index 5dd9db9..290cbe4 100644 --- a/lib/widgets/slider.dart +++ b/lib/widgets/slider.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; - class CustomSliderThumbCircle extends SliderComponentShape { final double thumbRadius; final int min; @@ -138,4 +137,4 @@ class CustomSliderThumbRect extends SliderComponentShape { String getValue(double value) { return ((max) * (value)).round().toString(); } -} \ No newline at end of file +} diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f16b4c3..df8d2f7 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..a924d8f --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "pagen"); + 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, "pagen"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/Podfile b/macos/Podfile index dade8df..9ec46f8 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fefda1d --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,765 @@ +// !$*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 */; }; + 7E8EB6746CA52C42DC9D2BEF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 674212336D8618C77FB6C45D /* 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 */ + 1504D8E0390C13C96B64B91C /* 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 = ""; }; + 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 /* pagen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = pagen.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 = ""; }; + 5E9D1941F574596CEB06F752 /* 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 = ""; }; + 674212336D8618C77FB6C45D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + ECAC43C6AE4AD1F43DDC8D2D /* 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 = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E8EB6746CA52C42DC9D2BEF /* 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 */, + DF4C10F4AEF5A15AF2602FF5 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* pagen.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 = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 674212336D8618C77FB6C45D /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + DF4C10F4AEF5A15AF2602FF5 /* Pods */ = { + isa = PBXGroup; + children = ( + ECAC43C6AE4AD1F43DDC8D2D /* Pods-Runner.debug.xcconfig */, + 1504D8E0390C13C96B64B91C /* Pods-Runner.release.xcconfig */, + 5E9D1941F574596CEB06F752 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 4D968C22B5DCD114E22A98CE /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 080246C2B5ADAEFCF1CC5588 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* pagen.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 */ + 080246C2B5ADAEFCF1CC5588 /* [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; + }; + 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"; + }; + 4D968C22B5DCD114E22A98CE /* [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; + }; +/* 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; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pagen.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pagen"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pagen.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pagen"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pagen.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pagen"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 0c4e30b..f024c51 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef64..b3c1761 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f..96d3fee 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" + "info": { + "version": 1, + "author": "xcode" }, - { - "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" - } -} + "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" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a..4f5212d 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png 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 index ed4cc16..40fea74 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png 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 index 483be61..1093ca1 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png 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 index bcbf36d..e98053b 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png 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 index 9c0a652..834c6ea 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png 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 index e71a726..70a1369 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png 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 index 8a31fe2..66f1d34 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 69d22db..6c7c318 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = hello_world +PRODUCT_NAME = PAGen // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.helloWorld +PRODUCT_BUNDLE_IDENTIFIER = com.kodsama.pagen // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2026 kodsama. All rights reserved. diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 4789daa..acd124f 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -6,14 +6,14 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) + CFBundleDisplayName + PAGen CFBundlePackageType APPL CFBundleShortVersionString diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 2722837..722fd92 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -10,6 +10,11 @@ class MainFlutterWindow: NSWindow { RegisterGeneratedPlugins(registry: flutterViewController) + title = + Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String + ?? "PAGen" + super.awakeFromNib() } } 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 185c224..27c80ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: fluttertoast: ^9.1.0 flutter_i18n: ^0.37.1 logger: ^2.7.0 + google_fonts: ^6.2.1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -50,6 +51,9 @@ flutter_launcher_icons: image_path: "assets/images/trollface.png" android: true ios: true + macos: + generate: true + image_path: "assets/images/trollface.png" # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/quote_grade_test.dart b/test/quote_grade_test.dart new file mode 100644 index 0000000..2cc1a08 --- /dev/null +++ b/test/quote_grade_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:pagen/models/quote.dart'; + +void main() { + test('copyWith bumps grade for thumb actions', () { + final quote = QuoteModel(id: 5, text: 'Test', grade: 0); + expect(quote.copyWith(grade: 1).grade, 1); + expect(quote.copyWith(grade: -1).grade, -1); + expect(quote.copyWith(grade: 2).grade, 2); + }); + + test('fromMap reads integer grade from sqlite-style values', () { + final quote = QuoteModel.fromMap({ + 'id': 1, + 'text': 'Hi', + 'grade': 3, + 'level': 1, + 'theme': 'Random', + }); + expect(quote.grade, 3); + }); +} diff --git a/test/void_test.dart b/test/void_test.dart index b90ce7c..809f5f0 100644 --- a/test/void_test.dart +++ b/test/void_test.dart @@ -27,5 +27,10 @@ void main() { expect(restored.source, quote.source); expect(restored.grade, quote.grade); }); + + test('copyWith updates grade for live UI refresh', () { + final quote = QuoteModel(id: 1, text: 'Hi', grade: 2); + expect(quote.copyWith(grade: 3).grade, 3); + }); }); } diff --git a/tool/ci.sh b/tool/ci.sh new file mode 100755 index 0000000..df70c5c --- /dev/null +++ b/tool/ci.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Run the same checks as GitHub Actions locally: +# ./tool/ci.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +if ! command -v flutter >/dev/null 2>&1; then + echo "error: flutter is not on PATH" >&2 + exit 1 +fi + +echo "==> Fetching dependencies" +flutter pub get + +echo "==> Checking formatting" +dart format --output=none --set-exit-if-changed lib test + +echo "==> Running static analysis" +dart analyze --fatal-infos + +echo "==> Running tests" +flutter test + +echo "All CI checks passed." diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 88b22e5..a962892 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES)