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)