From b10e698913f907a125035dc7b23006ecb04eebc1 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 30 Mar 2025 14:07:44 +0200 Subject: [PATCH] Refactor the way the releases are built This workflow is not manually triggered and can create automatically the appropriate tag. The build number is not increase to the next multiple of ten, to stay in sync with the iOS releases, which seem to cause more trouble and often need reuploads. The individual steps have been moved out to their own files, for better readability. We also now build the app for all supported platforms. --- .github/actions/flutter-common/action.yml | 23 ++ .github/workflows/build-android.yml | 73 +++++ .github/workflows/build-apple.yml | 47 +++ .github/workflows/build-linux.yml | 37 +++ .github/workflows/build-release.yml | 127 -------- .github/workflows/build-windows.yml | 26 ++ .github/workflows/bump-version.yml | 47 +++ .github/workflows/ci.yml | 14 +- .github/workflows/make-release.yml | 186 +++++++++++ fastlane/metadata/envfiles/decrypt_secrets.sh | 24 +- flatpak/scripts/flatpak_packager.dart | 48 ++- flatpak/scripts/flatpak_shared.dart | 196 ++++++------ ios/Podfile | 2 +- linux/CMakeLists.txt | 84 ++--- macos/Podfile.lock | 43 +-- windows/.gitignore | 17 ++ windows/CMakeLists.txt | 111 +++++++ windows/flutter/CMakeLists.txt | 109 +++++++ .../flutter/generated_plugin_registrant.cc | 23 ++ windows/flutter/generated_plugin_registrant.h | 15 + windows/flutter/generated_plugins.cmake | 28 ++ windows/runner/CMakeLists.txt | 40 +++ windows/runner/Runner.rc | 121 ++++++++ windows/runner/flutter_window.cpp | 71 +++++ windows/runner/flutter_window.h | 33 ++ windows/runner/main.cpp | 43 +++ windows/runner/resource.h | 16 + windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes windows/runner/runner.exe.manifest | 20 ++ windows/runner/utils.cpp | 65 ++++ windows/runner/utils.h | 19 ++ windows/runner/win32_window.cpp | 288 ++++++++++++++++++ windows/runner/win32_window.h | 102 +++++++ 33 files changed, 1783 insertions(+), 315 deletions(-) create mode 100644 .github/actions/flutter-common/action.yml create mode 100644 .github/workflows/build-android.yml create mode 100644 .github/workflows/build-apple.yml create mode 100644 .github/workflows/build-linux.yml delete mode 100644 .github/workflows/build-release.yml create mode 100644 .github/workflows/build-windows.yml create mode 100644 .github/workflows/bump-version.yml create mode 100644 .github/workflows/make-release.yml create mode 100644 windows/.gitignore create mode 100644 windows/CMakeLists.txt create mode 100644 windows/flutter/CMakeLists.txt create mode 100644 windows/flutter/generated_plugin_registrant.cc create mode 100644 windows/flutter/generated_plugin_registrant.h create mode 100644 windows/flutter/generated_plugins.cmake create mode 100644 windows/runner/CMakeLists.txt create mode 100644 windows/runner/Runner.rc create mode 100644 windows/runner/flutter_window.cpp create mode 100644 windows/runner/flutter_window.h create mode 100644 windows/runner/main.cpp create mode 100644 windows/runner/resource.h create mode 100644 windows/runner/resources/app_icon.ico create mode 100644 windows/runner/runner.exe.manifest create mode 100644 windows/runner/utils.cpp create mode 100644 windows/runner/utils.h create mode 100644 windows/runner/win32_window.cpp create mode 100644 windows/runner/win32_window.h diff --git a/.github/actions/flutter-common/action.yml b/.github/actions/flutter-common/action.yml new file mode 100644 index 00000000..181454e5 --- /dev/null +++ b/.github/actions/flutter-common/action.yml @@ -0,0 +1,23 @@ +name: 'Flutter common setup' +description: 'Common steps needed to setup the application' + +runs: + using: "composite" + steps: + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.29.2 + cache: true + + - name: Install Flutter dependencies + run: flutter pub get + shell: bash + + - name: Install Flutter dependencies + run: | + dart --version + flutter --version + shell: bash \ No newline at end of file diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 00000000..ff044875 --- /dev/null +++ b/.github/workflows/build-android.yml @@ -0,0 +1,73 @@ +name: Build Android +on: + workflow_call: + inputs: + ref: + required: true + type: string + secrets: + DECRYPTKEY_PLAYSTORE_SIGNING_KEY: + required: true + DECRYPTKEY_PROPERTIES: + required: true +jobs: + build_android_apk: + name: APK file + runs-on: ubuntu-latest + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Common flutter setup + uses: ./.github/actions/flutter-common + + - name: Decrypt config files + run: | + cd ./fastlane/metadata/envfiles + chmod +x ./decrypt_secrets.sh + ./decrypt_secrets.sh + shell: bash + env: + DECRYPTKEY_PLAYSTORE_SIGNING_KEY: ${{ secrets.DECRYPTKEY_PLAYSTORE_SIGNING_KEY }} + DECRYPTKEY_PROPERTIES: ${{ secrets.DECRYPTKEY_PROPERTIES }} + + - name: Build APK + run: flutter build apk --release + + - uses: actions/upload-artifact@v4 + with: + name: builds-apk + path: build/app/outputs/flutter-apk/app-release.apk + + build_android_aab: + name: AAB file + runs-on: ubuntu-latest + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Common setup + uses: ./.github/actions/flutter-common + + - name: Decrypt config files + run: | + cd ./fastlane/metadata/envfiles + chmod +x ./decrypt_secrets.sh + ./decrypt_secrets.sh + shell: bash + env: + DECRYPTKEY_PLAYSTORE: ${{ secrets.DECRYPTKEY_PLAYSTORE }} + DECRYPTKEY_PLAYSTORE_SIGNING_KEY: ${{ secrets.DECRYPTKEY_PLAYSTORE_SIGNING_KEY }} + DECRYPTKEY_PROPERTIES: ${{ secrets.DECRYPTKEY_PROPERTIES }} + + - name: Build AAB + run: flutter build appbundle --release + + - uses: actions/upload-artifact@v4 + with: + name: builds-aab + path: build/app/outputs/bundle/release/app-release.aab \ No newline at end of file diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml new file mode 100644 index 00000000..3ee33c9b --- /dev/null +++ b/.github/workflows/build-apple.yml @@ -0,0 +1,47 @@ +name: Build Apple +on: + workflow_call: + inputs: + ref: + required: true + type: string +jobs: + build_ios: + name: iOS + runs-on: macos-latest + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Common flutter setup + uses: ./.github/actions/flutter-common + + - name: Build .app + run: flutter build ios --release --no-codesign + + - uses: actions/upload-artifact@v4 + with: + name: builds-ios + path: build/ios/iphoneos/Runner.app + + build_macos: + name: macOS + runs-on: macos-latest + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Common flutter setup + uses: ./.github/actions/flutter-common + + - name: Build .app + run: flutter build macos --release + + - uses: actions/upload-artifact@v4 + with: + name: builds-macos + path: build/macos/Build/Products/Release/wger.app \ No newline at end of file diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml new file mode 100644 index 00000000..53e5ac18 --- /dev/null +++ b/.github/workflows/build-linux.yml @@ -0,0 +1,37 @@ +#name: Build Linux +on: + workflow_call: + inputs: + ref: + required: true + type: string +jobs: + build_linux: + name: Flathub + runs-on: ubuntu-latest + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Common setup + uses: ./.github/actions/flutter-common + + # Compare with list of available packages on the runner: + # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md + - name: Build application for linux + run: | + # ninja-build + sudo apt install -y pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev --no-install-recommends + flutter build linux --release + cd flatpak/scripts + dart pub get + dart flatpak_packager.dart --meta ../flatpak_meta.json --addTodaysVersion ${{inputs.ref}} + + - uses: actions/upload-artifact@v4 + with: + name: builds-linux + path: | + flatpak/scripts/flatpak_generator_exports/wger-linux-x86_64.tar.gz + flatpak/scripts/flatpak_generator_exports/wger-linux-x86_64.sha256 \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml deleted file mode 100644 index d5826690..00000000 --- a/.github/workflows/build-release.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Build release artefacts -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - -jobs: - deploy_android: - runs-on: ubuntu-latest - steps: - - name: Checkout application code - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'oracle' - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - flutter-version: 3.29.2 - - - name: Flutter info - run: | - dart --version - flutter --version - - - name: Install Flutter dependencies - run: flutter pub get - - - name: Decrypt config files - run: | - cd ./fastlane/metadata/envfiles - chmod +x ./decrypt_secrets.sh - ./decrypt_secrets.sh - env: - DECRYPTKEY_PLAYSTORE: ${{ secrets.DECRYPTKEY_PLAYSTORE }} - DECRYPTKEY_PLAYSTORE_SIGNING_KEY: ${{ secrets.DECRYPTKEY_PLAYSTORE_SIGNING_KEY }} - DECRYPTKEY_PROPERTIES: ${{ secrets.DECRYPTKEY_PROPERTIES }} - - - name: Extract version information - run: | - echo "VERSION_V=$(echo $GITHUB_REF | cut -d / -f 3)" >> $GITHUB_ENV - echo "VERSION=$(echo $GITHUB_REF | cut -d / -f 3 | cut -c 2-)" >> $GITHUB_ENV - echo "BUILD=$(flutter pub run cider version | cut -d '+' -f 2)" >> $GITHUB_ENV - - - name: Bump pubspec version - run: | - flutter pub run cider version ${{ env.VERSION }}+${{ env.BUILD }} - flutter pub run cider bump build - - - name: Build application for linux - run: | - sudo apt update - sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev - flutter build linux --release - cd flatpak/scripts - dart pub get - dart flatpak_packager.dart --meta ../flatpak_meta.json --github --addTodaysVersion ${{ env.VERSION }} - - - name: Build AAB - run: flutter build appbundle --release - - - name: Build APK - run: flutter build apk --release - - - name: Upload build to Play Store - run: | - bundle install - bundle exec fastlane android production - - - name: Make Github release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.VERSION }} - files: | - build/app/outputs/bundle/release/app-release.aab - build/app/outputs/flutter-apk/app-release.apk - flatpak/scripts/flatpak_generator_exports/wger-linux-x86_64.tar.gz - flatpak/scripts/flatpak_generator_exports/wger-linux-x86_64.sha256 - - - name: Generate flathub manifest - run: | - cd flatpak/scripts - dart pub get - dart manifest_generator.dart --meta ../flatpak_meta.json --github - mkdir ../../flathub - cp flatpak_generator_exports/de.wger.flutter.json ../../flathub - cp flatpak_generator_exports/flathub.json ../../flathub - - - name: Push updated config to flathub repository - uses: cpina/github-action-push-to-another-repository@main - env: - SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} - with: - source-directory: flathub - destination-github-username: wger-project - destination-repository-name: de.wger.flutter - user-email: github-actions@github.com - target-branch: release-${{ env.VERSION }} - create-target-branch-if-needed: true - commit-message: Update to ${{ env.VERSION }} - - # Note: the original tag that triggered the workflow is in the form vX.Y.Z - # but the pubspec.yaml is committed in the commit after that one. - # Since we need the tag to point to the correct commit for other workflows - # such as f-droid we need a way to correct it. Only moving the tag - # would not work, as it would trigger this workflow again. So as - # a workaround, we use the v-tag to trigger this workflow, add a new - # one without the v and push it. - - name: Commit pubspec version and delete tag - run: | - git config user.name Github-Actions - git config user.email github-actions@github.com - git checkout -b release-${{ env.VERSION }} - git add pubspec.yaml - git commit -m "Bump version to $( flutter pub run cider version )" - git push origin --delete ${{ env.VERSION_V }} - git push --set-upstream origin release-${{ env.VERSION }} \ No newline at end of file diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 00000000..0bfff14e --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,26 @@ +on: + workflow_call: + inputs: + ref: + required: true + type: string +jobs: + build_windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Common flutter setup + uses: ./.github/actions/flutter-common + + - name: Build .exe + run: flutter build windows --release + + - uses: actions/upload-artifact@v4 + with: + name: builds-windows + path: build\windows\x64\runner\Release\wger.exe \ No newline at end of file diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 00000000..27af7767 --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,47 @@ +on: + workflow_call: + inputs: + app_version: + required: true + type: string + +jobs: + determine_version: + name: Create tag + runs-on: ubuntu-latest + steps: + + - name: Checkout application + uses: actions/checkout@v4 + with: + fetch-depth: 0 # needed to push changes + + - name: Common flutter setup + uses: ./.github/actions/flutter-common + + - name: Validate version + run: | + RELEASE_VERSION="${{ inputs.app_version }}" + if [[ ! "${{ inputs.app_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Input version '${{ inputs.app_version }}' is not in X.Y.Z format." + exit 1 + fi + + # In order to make sure the build number is the same across all platforms, + # increase to the next multiple of 10. This is needed because the iOS process + # is a bit more brittle and might need to be repeated if the App needs + # to be re-submitted. + - name: Bump pubspec version + run: | + CURRENT_BUILD=$(flutter pub run cider version | cut -d '+' -f 2) + NEXT_BUILD=$(( (CURRENT_BUILD / 10 + 1) * 10 )) + flutter pub run cider version ${{ inputs.app_version }}+${NEXT_BUILD} + + - name: Commit pubspec + run: | + git config user.name Github-Actions + git config user.email github-actions@github.com + git add pubspec.yaml + git tag ${{ inputs.app_version }} + git commit -m "Bump version to $( flutter pub run cider version )" + git push origin HEAD:master diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5742187..89ff5404 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,21 +9,17 @@ on: paths: - '**.dart' - 'pubspec.yaml' + workflow_call: { } + jobs: test: + name: Run tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install Flutter - uses: subosito/flutter-action@v2 - with: - cache: true - channel: 'stable' - flutter-version: '3.29.x' - - - run: dart --version - - run: flutter --version + - name: Common flutter setup + uses: ./.github/actions/flutter-common - name: Install dependencies run: | diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml new file mode 100644 index 00000000..b64c9f81 --- /dev/null +++ b/.github/workflows/make-release.yml @@ -0,0 +1,186 @@ +name: Build release artefacts +on: + workflow_dispatch: + inputs: + version: + description: Version to tag and release (X.Y.Z) + required: true + type: string + +jobs: + + ci: + name: CI + uses: ./.github/workflows/ci.yml + + determine_version: + needs: ci + name: Prepare + uses: ./.github/workflows/bump-version.yml + with: + app_version: ${{ github.event.inputs.version }} + + build_linux: + name: Linux + needs: determine_version + uses: ./.github/workflows/build-linux.yml + with: + ref: ${{ github.event.inputs.version }} + + + build_android: + name: Android + needs: determine_version + uses: ./.github/workflows/build-android.yml + with: + ref: ${{ github.event.inputs.version }} + secrets: + DECRYPTKEY_PLAYSTORE_SIGNING_KEY: ${{ secrets.DECRYPTKEY_PLAYSTORE_SIGNING_KEY }} + DECRYPTKEY_PROPERTIES: ${{ secrets.DECRYPTKEY_PROPERTIES }} + + build_apple: + name: Apple + needs: determine_version + uses: ./.github/workflows/build-apple.yml + with: + ref: ${{ github.event.inputs.version }} + + build_windows: + name: Microsoft + needs: determine_version + uses: ./.github/workflows/build-windows.yml + with: + ref: ${{ github.event.inputs.version }} + + upload_play_store: + name: Upload to Play Store + runs-on: ubuntu-latest + needs: + - build_android + - build_linux + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.version }} + + - name: Download builds + uses: actions/download-artifact@v4 + with: + path: /tmp/ + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4 + + - name: Decrypt config files + run: | + cd ./fastlane/metadata/envfiles + chmod +x ./decrypt_secrets.sh + ./decrypt_secrets.sh + shell: bash + env: + DECRYPTKEY_PLAYSTORE: ${{ secrets.DECRYPTKEY_PLAYSTORE }} + + - name: Upload build to Play Store + run: | + mkdir -p ./build/app/outputs/bundle/release/ + cp /tmp/builds-aab/app-release.aab ./build/app/outputs/bundle/release/app-release.aab + bundle install + bundle exec fastlane android production + + # + # This is currently commented out since the iOS upload are currently being handled + # by the fastlane script in the ios folder. We use the account of pthaler for this + # and he runs the script manually. + # + + # upload_app_store: + # name: Upload to App Store (placeholder) + # runs-on: ubuntu-latest + # needs: + # - build_android + # - build_apple + # steps: + # - name: Checkout application + # uses: actions/checkout@v4 + # with: + # ref: feature/build-process + # # ref: ${{ github.event.inputs.version }} + # + # - name: Common flutter setup + # uses: ./.github/actions/flutter-common + # + # - name: Download builds + # uses: actions/download-artifact@v4 + # with: + # path: /tmp/ + # + # - name: Upload build to App Store + # run: | + # # bundle exec fastlane todo + + make_gh_release: + if: false + name: Make Github Release + runs-on: ubuntu-latest + needs: + - upload_play_store + - build_linux + - build_windows + - build_apple + + steps: + - name: Download builds + uses: actions/download-artifact@v4 + + - name: Make Github release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ inputs.version }} + files: | + builds-aab/app-release.aab + builds-apk/app-release.apk + builds-linux/wger-linux-x86_64.tar.gz + builds-linux/wger-linux-x86_64.sha256 + builds-ios/Runner.app + builds-macos/wger.app + + generate_fathub_manifest: + if: false + runs-on: ubuntu-latest + name: Upload flathub manifest + needs: + - make_gh_release + steps: + - name: Checkout application + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.version }} + + - name: Common flutter setup + uses: ./.github/actions/flutter-common + + - name: Generate manifest + run: | + cd flatpak/scripts + dart pub get + dart manifest_generator.dart --meta ../flatpak_meta.json --github + mkdir ../../flathub + cp flatpak_generator_exports/de.wger.flutter.json ../../flathub + cp flatpak_generator_exports/flathub.json ../../flathub + + - name: Push updated config to flathub repository + uses: cpina/github-action-push-to-another-repository@main + env: + SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} + with: + source-directory: flathub + destination-github-username: wger-project + destination-repository-name: de.wger.flutter + user-email: github-actions@github.com + target-branch: release-${{ github.event.inputs.version }} + create-target-branch-if-needed: true + commit-message: Update to ${{ github.event.inputs.version }} + diff --git a/fastlane/metadata/envfiles/decrypt_secrets.sh b/fastlane/metadata/envfiles/decrypt_secrets.sh index e44097a1..0d1c7983 100755 --- a/fastlane/metadata/envfiles/decrypt_secrets.sh +++ b/fastlane/metadata/envfiles/decrypt_secrets.sh @@ -6,14 +6,20 @@ # To encrypt a new version of the keys: # gpg -c filename.json -echo "decrypting playstore API keys" -gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PLAYSTORE" \ ---output ./playstore.json playstore.json.gpg +if [ -n "$DECRYPTKEY_PLAYSTORE" ]; then + echo "decrypting playstore API keys" + gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PLAYSTORE" \ + --output ./playstore.json playstore.json.gpg +fi -echo "decrypting key.properties" -gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PROPERTIES" \ ---output ./key.properties key.properties.gpg +if [ -n "$DECRYPTKEY_PROPERTIES" ]; then + echo "decrypting key.properties" + gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PROPERTIES" \ + --output ./key.properties key.properties.gpg +fi -echo "decrypting playstore signing keys" -gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PLAYSTORE_SIGNING_KEY" \ ---output ./keys.jks keys.jks.gpg \ No newline at end of file +if [ -n "$DECRYPTKEY_PLAYSTORE_SIGNING_KEY" ]; then + echo "decrypting playstore signing keys" + gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PLAYSTORE_SIGNING_KEY" \ + --output ./keys.jks keys.jks.gpg +fi \ No newline at end of file diff --git a/flatpak/scripts/flatpak_packager.dart b/flatpak/scripts/flatpak_packager.dart index 92cf2768..2caaf525 100644 --- a/flatpak/scripts/flatpak_packager.dart +++ b/flatpak/scripts/flatpak_packager.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_print import 'dart:io'; + import 'flatpak_shared.dart'; /// Creates an archive containing all the sources for the Flatpak package for a @@ -25,7 +26,8 @@ void main(List arguments) async { final metaIndex = arguments.indexOf('--meta'); if (metaIndex == -1) { throw Exception( - 'You must run this script with a metadata file argument, using the --meta flag.'); + 'You must run this script with a metadata file argument, using the --meta flag.', + ); } if (arguments.length == metaIndex + 1) { throw Exception('The --meta flag must be followed by the path to the metadata file.'); @@ -54,12 +56,16 @@ void main(List arguments) async { await outputDir.create(); final packageGenerator = PackageGenerator( - inputDir: metaFile.parent, meta: meta, addedTodaysVersion: addedTodaysVersion); + inputDir: metaFile.parent, + meta: meta, + addedTodaysVersion: addedTodaysVersion, + ); await packageGenerator.generatePackage( - outputDir, - await PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64, - fetchFromGithub); + outputDir, + await PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64, + fetchFromGithub, + ); } class PackageGenerator { @@ -67,10 +73,17 @@ class PackageGenerator { final FlatpakMeta meta; final String? addedTodaysVersion; - PackageGenerator({required this.inputDir, required this.meta, required this.addedTodaysVersion}); + const PackageGenerator({ + required this.inputDir, + required this.meta, + required this.addedTodaysVersion, + }); Future generatePackage( - Directory outputDir, CPUArchitecture arch, bool fetchReleasesFromGithub) async { + Directory outputDir, + CPUArchitecture arch, + bool fetchReleasesFromGithub, + ) async { final tempDir = await outputDir.createTemp('flutter_generator_temp'); final appId = meta.appId; @@ -79,7 +92,8 @@ class PackageGenerator { if (!(await desktopFile.exists())) { throw Exception( - 'The desktop file does not exist under the specified path: ${desktopFile.path}'); + 'The desktop file does not exist under the specified path: ${desktopFile.path}', + ); } await desktopFile.copy('${tempDir.path}/$appId.desktop'); @@ -101,12 +115,14 @@ class PackageGenerator { final origAppStreamFile = File('${inputDir.path}/${meta.appStreamPath}'); if (!(await origAppStreamFile.exists())) { throw Exception( - 'The app data file does not exist under the specified path: ${origAppStreamFile.path}'); + 'The app data file does not exist under the specified path: ${origAppStreamFile.path}', + ); } final editedAppStreamContent = AppStreamModifier.replaceVersions( - await origAppStreamFile.readAsString(), - await meta.getReleases(fetchReleasesFromGithub, addedTodaysVersion)); + await origAppStreamFile.readAsString(), + await meta.getReleases(fetchReleasesFromGithub, addedTodaysVersion), + ); final editedAppStreamFile = File('${tempDir.path}/$appId.metainfo.xml'); await editedAppStreamFile.writeAsString(editedAppStreamContent); @@ -117,7 +133,8 @@ class PackageGenerator { final buildDir = Directory(bundlePath); if (!(await buildDir.exists())) { throw Exception( - 'The linux build directory does not exist under the specified path: ${buildDir.path}'); + 'The linux build directory does not exist under the specified path: ${buildDir.path}', + ); } final destDir = Directory('${tempDir.path}/bin'); await destDir.create(); @@ -159,9 +176,10 @@ class AppStreamModifier { .replaceAll('\n', '<~>') .replaceFirst(RegExp(''), releasesSection) .replaceAll('<~>', '\n'); - } else { - return origAppStreamContent.replaceFirst( - '', '\n\t$releasesSection\n'); } + return origAppStreamContent.replaceFirst( + '', + '\n\t$releasesSection\n', + ); } } diff --git a/flatpak/scripts/flatpak_shared.dart b/flatpak/scripts/flatpak_shared.dart index 75ce96cf..eaf445a0 100644 --- a/flatpak/scripts/flatpak_shared.dart +++ b/flatpak/scripts/flatpak_shared.dart @@ -9,7 +9,7 @@ class Release { final String version; final String date; //TODO add resources - Release({required this.version, required this.date}); + const Release({required this.version, required this.date}); } enum CPUArchitecture { @@ -18,6 +18,7 @@ enum CPUArchitecture { final String flatpakArchCode; final String flutterDirName; + const CPUArchitecture(this.flatpakArchCode, this.flutterDirName); } @@ -27,11 +28,12 @@ class ReleaseAsset { final bool isRelativeLocalPath; final String tarballSha256; - ReleaseAsset( - {required this.arch, - required this.tarballUrlOrPath, - required this.isRelativeLocalPath, - required this.tarballSha256}); + const ReleaseAsset({ + required this.arch, + required this.tarballUrlOrPath, + required this.isRelativeLocalPath, + required this.tarballSha256, + }); } class Icon { @@ -72,9 +74,10 @@ class GithubReleases { Future _fetchReleasesAndAssets(bool canBeEmpty) async { final releaseJsonContent = (await http.get(Uri( - scheme: 'https', - host: 'api.github.com', - path: '/repos/$githubReleaseOrganization/$githubReleaseProject/releases'))) + scheme: 'https', + host: 'api.github.com', + path: '/repos/$githubReleaseOrganization/$githubReleaseProject/releases', + ))) .body; final decodedJson = jsonDecode(releaseJsonContent) as List; @@ -154,17 +157,19 @@ class GithubReleases { final res = List.empty(growable: true); if (x64TarballUrl != null && x64Sha != null) { res.add(ReleaseAsset( - arch: CPUArchitecture.x86_64, - tarballUrlOrPath: x64TarballUrl, - isRelativeLocalPath: false, - tarballSha256: x64Sha)); + arch: CPUArchitecture.x86_64, + tarballUrlOrPath: x64TarballUrl, + isRelativeLocalPath: false, + tarballSha256: x64Sha, + )); } if (aarch64TarballUrl != null && aarch64Sha != null) { res.add(ReleaseAsset( - arch: CPUArchitecture.aarch64, - tarballUrlOrPath: aarch64TarballUrl, - isRelativeLocalPath: false, - tarballSha256: aarch64Sha)); + arch: CPUArchitecture.aarch64, + tarballUrlOrPath: aarch64TarballUrl, + isRelativeLocalPath: false, + tarballSha256: aarch64Sha, + )); } return res.isEmpty ? null : res; } @@ -196,22 +201,22 @@ class FlatpakMeta { final String? githubReleaseProject; late final GithubReleases? _githubReleases; - FlatpakMeta( - {required this.appId, - required this.lowercaseAppName, - required this.githubReleaseOrganization, - required this.githubReleaseProject, - required List? localReleases, - required List? localReleaseAssets, - required this.localLinuxBuildDir, - required this.appStreamPath, - required this.desktopPath, - required this.icons, - required this.freedesktopRuntime, - required this.buildCommandsAfterUnpack, - required this.extraModules, - required this.finishArgs}) - : _localReleases = localReleases, + FlatpakMeta({ + required this.appId, + required this.lowercaseAppName, + required this.githubReleaseOrganization, + required this.githubReleaseProject, + required List? localReleases, + required List? localReleaseAssets, + required this.localLinuxBuildDir, + required this.appStreamPath, + required this.desktopPath, + required this.icons, + required this.freedesktopRuntime, + required this.buildCommandsAfterUnpack, + required this.extraModules, + required this.finishArgs, + }) : _localReleases = localReleases, _localReleaseAssets = localReleaseAssets { if (githubReleaseOrganization != null && githubReleaseProject != null) { _githubReleases = GithubReleases(githubReleaseOrganization!, githubReleaseProject!); @@ -219,16 +224,21 @@ class FlatpakMeta { } Future> getReleases( - bool fetchReleasesFromGithub, String? addedTodaysVersion) async { + bool fetchReleasesFromGithub, + String? addedTodaysVersion, + ) async { final releases = List.empty(growable: true); if (addedTodaysVersion != null) { releases.add(Release( - version: addedTodaysVersion, date: DateTime.now().toIso8601String().split('T').first)); + version: addedTodaysVersion, + date: DateTime.now().toIso8601String().split('T').first, + )); } if (fetchReleasesFromGithub) { if (_githubReleases == null) { throw Exception( - 'Metadata must include Github repository info if fetching releases from Github.'); + 'Metadata must include Github repository info if fetching releases from Github.', + ); } releases.addAll(await _githubReleases!.getReleases(addedTodaysVersion != null)); } else { @@ -246,71 +256,73 @@ class FlatpakMeta { if (fetchReleasesFromGithub) { if (_githubReleases == null) { throw Exception( - 'Metadata must include Github repository info if fetching releases from Github.'); + 'Metadata must include Github repository info if fetching releases from Github.', + ); } return _githubReleases!.getLatestReleaseAssets(); - } else { - if (_localReleases == null) { - throw Exception('Metadata must include releases if not fetching releases from Github.'); - } - return _localReleaseAssets; } + if (_localReleases == null) { + throw Exception('Metadata must include releases if not fetching releases from Github.'); + } + return _localReleaseAssets; } static FlatpakMeta fromJson(File jsonFile, {bool skipLocalReleases = false}) { try { final dynamic json = jsonDecode(jsonFile.readAsStringSync()); return FlatpakMeta( - appId: json['appId'] as String, - lowercaseAppName: json['lowercaseAppName'] as String, - githubReleaseOrganization: json['githubReleaseOrganization'] as String?, - githubReleaseProject: json['githubReleaseProject'] as String?, - localReleases: skipLocalReleases - ? null - : (json['localReleases'] as List?)?.map((dynamic r) { - final rMap = r as Map; - return Release(version: rMap['version'] as String, date: rMap['date'] as String); - }).toList(), - localReleaseAssets: skipLocalReleases - ? null - : (json['localReleaseAssets'] as List?)?.map((dynamic ra) { - final raMap = ra as Map; - final archString = raMap['arch'] as String; - final arch = (archString == CPUArchitecture.x86_64.flatpakArchCode) - ? CPUArchitecture.x86_64 - : (archString == CPUArchitecture.aarch64.flatpakArchCode) - ? CPUArchitecture.aarch64 - : null; - if (arch == null) { - throw Exception( - 'Architecture must be either "${CPUArchitecture.x86_64.flatpakArchCode}" or "${CPUArchitecture.aarch64.flatpakArchCode}"'); - } - final tarballFile = - File('${jsonFile.parent.path}/${raMap['tarballPath'] as String}'); - final tarballPath = tarballFile.absolute.path; - final preShasum = Process.runSync('shasum', ['-a', '256', tarballPath]); - final shasum = preShasum.stdout.toString().split(' ').first; - if (preShasum.exitCode != 0) { - throw Exception(preShasum.stderr); - } - return ReleaseAsset( - arch: arch, - tarballUrlOrPath: tarballPath, - isRelativeLocalPath: true, - tarballSha256: shasum); - }).toList(), - localLinuxBuildDir: json['localLinuxBuildDir'] as String, - appStreamPath: json['appStreamPath'] as String, - desktopPath: json['desktopPath'] as String, - icons: (json['icons'] as Map).entries.map((mapEntry) { - return Icon(type: mapEntry.key as String, path: mapEntry.value as String); - }).toList(), - freedesktopRuntime: json['freedesktopRuntime'] as String, - buildCommandsAfterUnpack: (json['buildCommandsAfterUnpack'] as List?) - ?.map((dynamic bc) => bc as String) - .toList(), - extraModules: json['extraModules'] as List?, - finishArgs: (json['finishArgs'] as List).map((dynamic fa) => fa as String).toList()); + appId: json['appId'] as String, + lowercaseAppName: json['lowercaseAppName'] as String, + githubReleaseOrganization: json['githubReleaseOrganization'] as String?, + githubReleaseProject: json['githubReleaseProject'] as String?, + localReleases: skipLocalReleases + ? null + : (json['localReleases'] as List?)?.map((dynamic r) { + final rMap = r as Map; + return Release(version: rMap['version'] as String, date: rMap['date'] as String); + }).toList(), + localReleaseAssets: skipLocalReleases + ? null + : (json['localReleaseAssets'] as List?)?.map((dynamic ra) { + final raMap = ra as Map; + final archString = raMap['arch'] as String; + final arch = (archString == CPUArchitecture.x86_64.flatpakArchCode) + ? CPUArchitecture.x86_64 + : (archString == CPUArchitecture.aarch64.flatpakArchCode) + ? CPUArchitecture.aarch64 + : null; + if (arch == null) { + throw Exception( + 'Architecture must be either "${CPUArchitecture.x86_64.flatpakArchCode}" or "${CPUArchitecture.aarch64.flatpakArchCode}"', + ); + } + final tarballFile = + File('${jsonFile.parent.path}/${raMap['tarballPath'] as String}'); + final tarballPath = tarballFile.absolute.path; + final preShasum = Process.runSync('shasum', ['-a', '256', tarballPath]); + final shasum = preShasum.stdout.toString().split(' ').first; + if (preShasum.exitCode != 0) { + throw Exception(preShasum.stderr); + } + return ReleaseAsset( + arch: arch, + tarballUrlOrPath: tarballPath, + isRelativeLocalPath: true, + tarballSha256: shasum, + ); + }).toList(), + localLinuxBuildDir: json['localLinuxBuildDir'] as String, + appStreamPath: json['appStreamPath'] as String, + desktopPath: json['desktopPath'] as String, + icons: (json['icons'] as Map).entries.map((mapEntry) { + return Icon(type: mapEntry.key as String, path: mapEntry.value as String); + }).toList(), + freedesktopRuntime: json['freedesktopRuntime'] as String, + buildCommandsAfterUnpack: + (json['buildCommandsAfterUnpack'] as List?)?.map((dynamic bc) => bc as String).toList(), + extraModules: json['extraModules'] as List?, + finishArgs: (json['finishArgs'] as List).map((dynamic fa) => fa as String).toList(), + ); } catch (e) { throw Exception('Could not parse JSON file, due to this error:\n$e'); } diff --git a/ios/Podfile b/ios/Podfile index 2c068c40..0b62d79e 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '14.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index e60424b5..dd06331c 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -5,6 +5,7 @@ project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "wger") + # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "de.wger.flutter") @@ -17,22 +18,22 @@ cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endif() +if (FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif () # Define build configuration options. -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") -endif() +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif () # Compilation settings that should be applied to most targets. # @@ -40,14 +41,15 @@ endif() # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_features(${TARGET} PUBLIC cxx_std_14) - # Workaround for https://github.com/wger-project/flutter/issues/577 / https://github.com/rive-app/rive-flutter/issues/390 - #target_compile_options(${TARGET} PRIVATE -Wall -Werror) -- original options - target_compile_options(${TARGET} PRIVATE -Wall -Werror -Wno-unused-variable -Wno-unused-function) + # Roland: + # Workaround for https://github.com/wger-project/flutter/issues/577 / https://github.com/rive-app/rive-flutter/issues/390 + #target_compile_options(${TARGET} PRIVATE -Wall -Werror) -- original options + target_compile_options(${TARGET} PRIVATE -Wall -Werror -Wno-unused-variable -Wno-unused-function) - target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") - target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() # Flutter library and tool build rules. @@ -65,9 +67,9 @@ add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # # 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" + "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 @@ -86,8 +88,8 @@ add_dependencies(${BINARY_NAME} flutter_assemble) # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding @@ -99,9 +101,9 @@ include(flutter/generated_plugins.cmake) # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif () # Start with a clean build bundle directory every time. install(CODE " @@ -112,19 +114,19 @@ set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) + COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) + COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) + COMPONENT Runtime) -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endforeach(bundled_library) +foreach (bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach (bundled_library) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. @@ -133,10 +135,10 @@ install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() +if (NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif () diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 972e7588..b429ea9d 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -14,20 +14,21 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqlite3 (3.47.0): - - sqlite3/common (= 3.47.0) - - sqlite3/common (3.47.0) - - sqlite3/dbstatvtab (3.47.0): + - sqlite3 (3.49.1): + - sqlite3/common (= 3.49.1) + - sqlite3/common (3.49.1) + - sqlite3/dbstatvtab (3.49.1): - sqlite3/common - - sqlite3/fts5 (3.47.0): + - sqlite3/fts5 (3.49.1): - sqlite3/common - - sqlite3/perf-threadsafe (3.47.0): + - sqlite3/perf-threadsafe (3.49.1): - sqlite3/common - - sqlite3/rtree (3.47.0): + - sqlite3/rtree (3.49.1): - sqlite3/common - sqlite3_flutter_libs (0.0.1): + - Flutter - FlutterMacOS - - sqlite3 (~> 3.47.0) + - sqlite3 (~> 3.49.1) - sqlite3/dbstatvtab - sqlite3/fts5 - sqlite3/perf-threadsafe @@ -46,7 +47,7 @@ DEPENDENCIES: - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - rive_common (from `Flutter/ephemeral/.symlinks/plugins/rive_common/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`) + - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) @@ -70,25 +71,25 @@ EXTERNAL SOURCES: shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin sqlite3_flutter_libs: - :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos + :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos video_player_avfoundation: :path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin SPEC CHECKSUMS: - file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d - flutter_zxing: ea030278aa2a051cd81a5b0053be9d0569d11c9a + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 + flutter_zxing: 91e9d17c79c60860450e8879cced0ec54f6a2601 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - rive_common: 0f0aadf670f0c6a7872dfe3e6186f112a5319108 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqlite3: 0aa20658a9b238a3b1ff7175eb7bdd863b0ab4fd - sqlite3_flutter_libs: f0b7a85544d8bac7b8bac12eac7d05bcfdd786d0 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + rive_common: ea79040f86acf053a2d5a75a2506175ee39796a5 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 + sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0 -COCOAPODS: 1.16.0 +COCOAPODS: 1.16.2 diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..6d37811b --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,111 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(wger LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "wger") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if (IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else () + if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif () +endif () +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + + # Workaround for https://github.com/wger-project/flutter/issues/577 / https://github.com/rive-app/rive-flutter/issues/390 + # target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") --> Original options + target_compile_options(${TARGET} PRIVATE /W4 /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif () + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if (PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif () + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..3a31fc83 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + RivePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RivePlugin")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..fa00096b --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,28 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows + rive_common + sqlite3_flutter_libs + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_zxing +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 00000000..b0bcb7cb --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "de.wger" "\0" + VALUE "FileDescription", "wger" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "wger" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 de.wger. All rights reserved." "\0" + VALUE "OriginalFilename", "wger.exe" "\0" + VALUE "ProductName", "wger" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 00000000..99e94cad --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"wger", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..a42ea768 --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_